大致题意: 给定一个序列,每次操作你可以选择一段区间,然后将其中所有数/所有下标为奇数的数/所有下标为偶数的数都减(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
",ans+a[n]);//注意最后答案加上a[n]
}return 0;
}