Description
给定一个长度为 (n) 的非负整数序列 (a_1,a_2,...,a_n)。每一步可以选择任意一种操作执行:
- 选择一个区间,将其中所有数 (-1)。
- 选择一个区间,将其中所有下标为奇数的数 (-1)。
- 选择一个区间,将其中所有下标为偶数的数 (-1)。
求至少多少步能将整个序列变成 (0)。
Solution
显然每次只需要考虑开头的相邻两位即可。
刚开始,连直线 (min(a[1],a[2])),跳线 (a[1]-min(a[1],a[2])),这样 (a[1]) 完成。
但后续,由于会有前面的线连过来,因此我们维护 (3) 个标记 (c_1,c_2,c_3) 分别表示从 (i-1) 连到 (i) 的直线条数,落在 (i) 上的跳线条数和落在 (i-1) 上的跳线条数。
如果前面连过来的线过多而不能容纳,我们需要先清理掉一些,这时可能会出现一些位置不知道该留直线还是留跳线,用一个 (res) 变量记下来,之后通过等效操作将其延期处理。
删掉被前面线覆盖掉的部分之后,计算新增的直线条数,并修改对应变量。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int n,a[N];
void solve()
{
memset(a,0,sizeof a);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int cnt1=0; // 直线个数
int cnt2=0; // 跳线个数
int cnt3=0; // 奇偶性为另一类的跳线个数
int ans=0; // 答案
int res=0; // 延迟标记
for(int i=2;i<=n+1;i++)
{
// 如果前面的线过多
if(a[i]<cnt1+cnt2)
{
int delta=cnt1+cnt2-a[i]; // 多余的线的数目
if(cnt2<delta)
cnt1-=delta-cnt2, delta-=delta-cnt2; // 全删跳线不够,删掉一些直线
if(cnt1<delta)
cnt2-=delta-cnt1, delta-=delta-cnt1; // 全删直线不够,删掉一些跳线
// 先两边都删
cnt2-=delta;
cnt1-=delta;
// 由于有了 delta 条免费线
res=delta;
a[i]-=delta;
}
// 删掉前面线覆盖掉的部分
a[i]-=cnt1+cnt2;
int add=min(a[i-1],a[i]); // 新增的直线的个数
// 处理直线
ans+=add;
a[i-1]-=add;
a[i]-=add;
cnt1+=add;
// 处理跳线
ans+=a[i-1];
cnt3+=a[i-1];
a[i-1]=0;
// 处理标记,留着下次决策
a[i]+=res; // 先把位置空出来
ans-=res; // 先退钱
res=0;
swap(cnt2,cnt3);
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--) solve();
}