[ZJOI2020] 序列

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();
}

原文地址:https://www.cnblogs.com/mollnn/p/13679532.html