【CFGym101623J】Juggling Troupe(结论+模拟)
- 有一排 \(n\) 个数字 \(a_{1\sim n}\)。
- 每一时刻,所有大于等于 \(2\) 的 \(a_i\) 会同时减 \(2\),并令 \(a_{i-1},a_{i+1}\) (若存在)分别加 \(1\)。
- 求最终的 \(a_{1\sim n}\)。
- \(1\le n\le 10^6\),\(a_i\in\{0,1,2\}\)
重要结论
首先,我们发现虽然题目中强调了所有大于等于 \(2\) 的 \(a_i\) 要同时减 \(2\) 并将两侧数加 \(1\),但实际上以任意顺序进行这个操作都不会影响答案。
更有甚者,我们可以先将所有初始等于 \(2\) 的 \(a_i\) 视作 \(0\),然后将它们一个一个加上 \(2\) 并执行操作,同样不会影响答案。
这样一来,我们只要考虑初始只有一个 \(2\) 的情况就可以了。
初始只有一个 \(2\)
由于 \(2\) 会让两侧的数加上 \(1\),所以只要周围还存在 \(1\),就会产生新的 \(2\)。
考虑手玩找规律。假设初始有一串数:
按题目要求减 \(2\) 并将两侧数加 \(1\):
先考虑一端:
发现这一过程就是不断将 \(20\) 左移的过程,所以最终得到:
除去第一项之后又是一个 \(01\cdots121\cdots10\) 的形式,套用这个结果得到:
发现这一过程就是不断在左侧加 \(1\),并将 \(2\) 右移。
最后得到:
其中左侧的 \(0\) 和 \(2\) 之间的 \(1\) 的个数与最初左侧的 \(0\) 和 \(2\) 之间的 \(1\) 的个数相同。
此时再对 \(2\) 操作,又是一个不断将 \(20\) 左移的过程:
然后发现,此时 \(0\) 左侧的 \(1\) 的个数为最初右侧的 \(0\) 和 \(2\) 之间的 \(1\) 的个数加 \(1\),\(0\) 右侧的 \(1\) 的个数为最初左侧的 \(0\) 和 \(2\) 之间的 \(1\) 的个数加 \(1\)。
也就是给这个位置和对称位置减 \(1\),两侧第一个 \(0\) 加 \(1\)。
模拟
根据以上结论,我们维护好每个极长的 \(1\) 连续段(特殊地,由于 \(2\) 可以在之后再加,因此 \(3\) 也视作 \(1\))。
从左到右枚举每一位,只要它为 \(2\) 或 \(3\),就按照上面的结论模拟一下,维护出新的极长的 \(1\) 连续段即可。
代码:\(O(n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 1000000
using namespace std;
int n,a[N+5],w[N+5],l1[N+5],r1[N+5],l2[2*N+5],r2[2*N+5];char s[N+5];
int main()
{
RI i,x=1,c1=0,c2=N;scanf("%s",s+1),n=strlen(s+1);
for(i=1;i<=n;++i) (a[i]=s[i]&15)^1&&(x^i&&(l2[++c2]=x,r2[c2]=i-1),x=i+1);x<=n&&(l2[++c2]=x,r2[c2]=n);//预处理极长的1连续段
RI p=N+1,f1,f2;for(i=1;i<=n;++i)
{
a[i]&1&&(l2[p]<i&&(l1[++c1]=l2[p],r1[c1]=i-1),r2[p]>i?l2[p]=i+1:++p);W(a[i])//从连续段中破开
{
c1&&r1[c1]==i-1?(f1=r1[c1]-l1[c1]+1,--c1):(f1=0),p<=c2&&l2[p]==i+1?(f2=r2[p]-l2[p]+1,++p):(f2=0);//提取左右连续段
if(a[i]==1) {l2[--p]=i-f1,r2[p]=i+f2;break;}--a[i],--a[x=i-f1+f2];//等于1时不操作,直接合并;给当前位置和对称位置加1
if(i-f1-1&&(++a[i-f1-1])&1) if(++f1,c1&&r1[c1]==i-f1-1) f1+=r1[c1]-l1[c1]+1,--c1;//给左侧第一个0加1
if(i+f2+1<=n&&(++a[i+f2+1])&1) if(++f2,p<=c2&&l2[p]==i+f2+1) f2+=r2[p]-l2[p]+1,++p;//给右侧第一个0加1
if(x<i) i-f1<x&&(l1[++c1]=i-f1,r1[c1]=x-1),x<i-1&&(l1[++c1]=x+1,r1[c1]=i-1);else f1&&(l1[++c1]=i-f1,r1[c1]=i-1);//处理出左侧连续段
if(x>i) x<i+f2&&(l2[--p]=x+1,r2[p]=i+f2),i+1<x&&(l2[--p]=i+1,r2[p]=x-1);else f2&&(l2[--p]=i+1,r2[p]=i+f2);//处理出右侧连续段
}
End:if(p<=c2&&r2[p]==i) l1[++c1]=l2[p],r1[c1]=r2[p++];//右侧连续段被移到了左侧
}
for(i=1;i<=n;++i) putchar(48|a[i]);return 0;
}