2024/1/22 算法笔记
1.差分的应用
我们知道,差分的本质是一个数组前一个元素和后一个元素的差值的列表。
如果一个数组中每一个元素都相同,那么差分数组元素就都是0;
我们有一个问题是进行多次区间增值或减值后,至少要多少次,才能使数组中所有元素大小都相同。这里我们就可以应用上面的原则。
对原数组求差分数组,讲差分数组的负数元素绝对值加到一个sum1里,将正数元素的绝对值加到一个sum2 里,那么实际上我们的答案就是:ans = min(sum1 , sum2) + abs(sum1 - sum2)
另外一个问题是在最少次数的约束下,可以生成多少种不同的数组?(原数组处理过后的数组)
实际意义:
得到的数列有多少种,其实就是问的b[1]可以有多少种
我们上述所有操作是与b[1]无关的,因为我们的目标是让除了b[1]以外的项变0,所
以我们上述的操作没有考虑到b[1],b[1]怎么变,与我们求出的最小步骤无关
那么,我们怎么知道b[1]有几种呢?很简单,其实就是看看有几种一步步减或一步步
加的操作数,因为我们一步步加的时候(假设我们现在的操作对象下标为i),
可以这样操作,b[1]-1,b[i]+1 , 也就是说操作差分数组的时候总是从第一个元素开始到第i个元素。
一步步减的时候可以这样操作,b[1]+1,b[i]-1
(注意,一个差分序列里一步步操作的时候只可能一步步加或一步步减,不可能一步
步加和一步步减同时存在)
所以说,有几步一步步的操作就有几种情况+1,为什么+1呢,因为这个b[1]本身就有
一个值啊!就算你不对他进行任何操作,它自己也有一种情况。
一加一减(也就是我们所说的一步顶两步的操作)操作数为min(p,q)
那么一步步的操作数就为max(p,q)-min(p,q)=abs(p - q)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
LL n,c,p,q,a[100010];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
for(int i=2;i<=n;i++)
{
c=a[i]-a[i-1];
if(c>0)
{
p+=c;
}
else
q-=c;
}
LL ans1=max(p,q);
LL ans2=abs(p-q)+1;
cout<<ans1<<endl<<ans2;
return 0;
}
2.单调栈
维护一个栈,保证里面的元素按照一个固有的顺序排序。
[P2866 USACO06NOV] Bad Hair Day S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
维护方法是维护栈内从栈底到栈顶的元素大小逐渐减小,方法很显然是遇到大的将要放入栈中的元素,首先将站内比这个元素小的元素出栈。然后每放入一个元素就记录一次size,累计ans。
3. 双指针
又做了一个经典的滑动窗口的题
唯一的雪花 Unique Snowflakes - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
我们的任务使维护一个min值。
使队列里的元素都是独立唯一的。
事实上更应该称呼它为双指针。
int a[1000005];
void solve(){
int n;
cin>>n;
map<int,int> mp;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int ans = 0;
int l=1,r=1;
int cnt = 1;
mp[a[1]]=1;
while(r<n){
r++;
mp[a[r]]++;
if(mp[a[r]]==1) {
cnt++;
continue;
}
else{
ans= max(ans,cnt);
mp[a[l]]--;
if(mp[a[l]]==0) cnt--;
l++;
}
}
cout<<ans<<endl;
} //因为要用uva测,所以不太清楚结果如何,应该是对的。
更新于1.22:
[P3143 USACO16OPEN] Diamond Collector S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
也是一个双指针题目,先对钻石大小排序,对于每一个钻石,求符合要求的 左右两个柜子能放多少个钻石。
4.滑动窗口 (单调队列)
这次是真的单调队列题 虽然是模板题
给定一个固定大小的窗口,和一个数组,将这个窗口在数组种滑动,让其维护一些属性,比如窗口中的最大最小值,是否有重复元素等等。
具体做法是使用双端队列deque
结合代码来讲。这一份是求每走一步,窗口中的最大值的问题。
int a[1000005];
void solve(){
int n,k;
cin>>n>>k;
deque<int>q;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
//当窗口左端到了需要删除的时候,删除元素直到大于等于i-k+1的部分
while(!q.empty() && i-k+1>q.front()) q.pop_front();
//当队列右端元素小于当前要插入的元素的时候,我们就将这个下标删除掉。直到能将这个a[i]放入队列为止。
while(!q.empty() && a[q.back()] < a[i]) q.pop_back();
q.push_back(i);
if(i>=k) cout<<a[q.front()]<<endl; //当i第一次到k的长度的时候,开始每次遍历输出一个最大值。因为比这个front小的都被删除了。
}
}
5. 其他
更新于1.22:
一个有意思的题,[P2671 NOIP2015 普及组] 求和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
是分块分奇偶处理的一个问题,可以将复杂度降到O(n)
不过我想到了分块和分奇偶,没有推出求结果的公式,最后靠题解的公式过的,菜的抠脚
代码可以看提交记录(
另外,思路和公式的推导可以查看最后一篇题解,非常易懂,比前面几篇好。
另外一个是单调栈的问题,[P3467 POI2008] PLA-Postering - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
求最少能用多少个海报覆盖这些矩形组合。这个覆盖是可以跨楼覆盖的,按题意来看。
不考虑宽度,我们对于长度,有一个假设,
- 如果每一个楼的高度都不一样,我们就需要n张海报,也就是高楼数n。
- 如果有两座高度相同的高楼,我们就可以少用一张海报。
- 如果h( i ) > h( i + 1 ) ,我们就确定这个突出来的部分是一定要用到
相当于我们关心的是高度相同的楼能为我们节省多少张海报。(高度相同即可,不需要相邻),但是我们不能盖住空气,所以后一个h应该高于或等于前一个h。
象形的说就是凹形不能节省,凸形可以节省。
那么单调栈如何使用
void solve(){
int n;
cin>>n;
stack<LL>s;
LL num =0;
for(int i=1;i<=n;i++){
LL x,y;
cin>>x>>y;
//保证栈里面 的 高度都是递增的,这样,栈里面的元素,就是上升形的,就要用size张海报覆盖。
while(!s.empty() && y<=s.top()){
if(y == s.top()) num++;
s.pop();
}
s.push(y);
}
cout<<n-num<<endl;
}
今天宿舍里只剩俺了,暴打2h农结果7连跪,直接卸载。
总之这几天先补题,玩游戏的事,等过年看看。