【洛谷6631】[ZJOI2020] 序列(思维题)
大致题意: 给定一个序列,每次操作你可以选择一段区间,然后将其中所有数/所有下标为奇数的数/所有下标为偶数的数都减\(1\),求最少操作多少次能够让全部数都变成\(0\)。
前言
一道很妙的题目,看完题解感觉很简单,但比赛的时候真的是只会打暴力。。。
从第一个位置开始考虑
令将区间内所有数减\(1\)的操作为第一类操作,将区间内所有下标为奇数/偶数的数减\(1\)的操作为第二类操作。
对于第一个位置,处理所有以它为区间左端点的操作,显然我们需要将它减成\(0\)。
我们分三步贪心:尽可能进行第一类操作(进行\(\min\{a_1,a_2\}\)次),尽可能进行第二类操作(进行\(\min\{a_1,a_3\}\)次),将第一个数减至\(0\)(进行\(a_1\)次)。
贪心的正确性在于,无论何时开始一种新的操作都要付出\(1\)的代价,而优先进行第一类操作肯定不会使答案变劣。
扩展到全局
考虑把\(a_1\)变成\(0\)之后,紧接着就可以去按类似的步骤处理\(a_2\),然后是\(a_3,a_4,...,a_n\)。
但问题在于当前处理到的位置可能先前已经进行过某些操作了。
设\(cur,now\)分别表示当前位置进行过的第一类操作和第二类操作数。(这里我们枚举的是\(a_2\))
首先,自然要将\(cur\)和\(now\)分别向\(a_i\)取\(min\)。
然后,如果\(cur+now<a_i\),显然可以直接将\(a_i\)减去\(cur+now\)。
否则,我们令\(k=cur+now-a_i\),显然\(cur-k\)次第一类操作和\(now-k\)次第二类操作都是必然要执行的,而剩余的\(k\)次操作可以免费任选第一类操作或是第二类操作。
为了解决这样的问题,我们可以将答案先减去\(k\),然后再操作完之后把\(a_i\)修改回\(k\),表示免费任选。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
int main()
{
RI Tt,i,t,k,cur,now,nxt;long long ans;F.read(Tt);W(Tt--)
{
for(F.read(n),i=1;i<=n;++i) F.read(a[i]);
for(cur=now=nxt=ans=0,i=2;i<=n;++i,swap(now,nxt))//每次交换now和nxt
cur>a[i]&&(cur=a[i]),now>a[i]&&(now=a[i]),//向a[i]取min
cur+now>a[i]?(k=cur+now-a[i],cur-=k,now-=k,a[i]=0):(k=0,a[i]-=cur+now),//若cur+now>a[i]可以免费任选k次操作,否则直接减去
ans+=(t=min(a[i-1],a[i])),a[i-1]-=t,a[i]-=t,cur+=t,ans+=a[i-1],nxt+=a[i-1],//优先进行第一类操作,然后是第二类操作
k&&(ans-=k,a[i]=k);//免费任选k次操作的处理
printf("%lld\n",ans+a[n]);//注意最后答案加上a[n]
}return 0;
}