【ZJOI2020】序列【贪心】
Description
有一个长度为 \(n\) 的非负整数序列 \(a_1, a_2, \dots, a_n\)。每一步你可以从以下三种操作中选择一种执行:
- 选择一个区间 \([l, r]\),将下标在这个区间里的所有数都减 1。
- 选择一个区间 \([l, r]\),将下标在这个区间里且下标为奇数的所有数都减 1。
- 选择一个区间 \([l, r]\),将下标在这个区间里且下标为偶数的所有数都减 1。
求最少需要多少步才能将序列中的所有数都变成 0。
\(n\le 10^5,a_i\le 10^9\)。
Solution
首先题目的三种操作可以转化为两种操作:
- 从某一点出发,尽可能向右延伸直到到达为 \(0\) 的位置,将经过的点全部减 \(1\)。
- 从某一点出发,依次将右侧与当前点奇偶性相同的点 \(-1\) ,直到到达为 \(0\) 的位置。
考虑从左到右,将所有数变为 \(0\),设当前正在考虑 \(a_i\),如果 \(a_{i+1}\) 与 \(a_i\) 都不为 \(0\),那么直接从 \(i\) 出发执行一操作一定比执行二操作优,于是我们一直执行一操作直到 \(a_i\) 为 \(0\) 或 \(a_{i+1}\) 为 \(0\);然后若 \(a_i\not= 0\),再用二操作将 \(a_i\) 减到 \(0\)。
由于前面的操作会对当前点造成影响,不妨设可能会影响到当前点的一操作有 \(x\) 种,二操作有 \(y\) 种。
若 \(a_i>x+y\) ,那么直接减去 \(x+y\)。否则 \(x,y\) 中有部分操作在这里就终止了,我们令 \(k=x+y-a_i\),然后先让 \(x,y,a_i\) 都减去 \(k\),然后看作给 \(a_i\) 增加了 \(k\) 次免费操作的机会,让它来决定是选一操作还是二操作,问题就迎刃而解了。但是 \(x\) 有可能小于 \(k\),那么 \(k-x\) 个二操作一定延伸不了,直接让 \(y-=k-x,k=x\) 即可。\(y<k\) 时同理。
最终复杂度 \(\mathcal O(n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int n,T;
ll a[N],ans;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
ll x=0,y=0,z=0;ans=0;
for(int i=2;i<=n+1;++i){
int d=0;
if(a[i]<x+y){
d=x+y-a[i];
if(x<d) y-=d-x,d=x;
if(y<d) x-=d-y,d=y;
x-=d;y-=d;a[i]-=d;
}
a[i]-=x+y;
ll tmp=min(a[i],a[i-1]);
x+=tmp;
a[i-1]-=tmp;a[i]-=tmp;ans+=tmp;
tmp=a[i-1];
z+=tmp;ans+=tmp;a[i-1]=0;
ans-=d;a[i]+=d;
swap(y,z);
}
printf("%lld\n",ans);
}
return 0;
}