把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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\)

考虑手玩找规律。假设初始有一串数:

\[011111\cdots11111211111\cdots111110 \]

按题目要求减 \(2\) 并将两侧数加 \(1\)

\[011111\cdots11112021111\cdots111110 \]

先考虑一端:

\[011111\cdots11120121111\cdots111110\\ 011111\cdots11201121111\cdots111110\\ 011111\cdots12011121111\cdots111110\\ \cdots\cdots \]

发现这一过程就是不断将 \(20\) 左移的过程,所以最终得到:

\[020111\cdots11111121111\cdots111110\\ 101111\cdots11111121111\cdots111110 \]

除去第一项之后又是一个 \(01\cdots121\cdots10\) 的形式,套用这个结果得到:

\[110111\cdots11111112111\cdots111110\\ 111011\cdots11111111211\cdots111110\\ \cdots\cdots \]

发现这一过程就是不断在左侧加 \(1\),并将 \(2\) 右移。

最后得到:

\[11111\cdots11011\cdots111120 \]

其中左侧的 \(0\)\(2\) 之间的 \(1\) 的个数与最初左侧的 \(0\)\(2\) 之间的 \(1\) 的个数相同。

此时再对 \(2\) 操作,又是一个不断将 \(20\) 左移的过程:

\[11111\cdots11020\cdots111111\\ 11111\cdots11101\cdots111111 \]

然后发现,此时 \(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;
}
posted @ 2022-01-24 19:29  TheLostWeak  阅读(33)  评论(0编辑  收藏  举报