[2018湖南省队集训] 6.28 T3 simulate

时间:2021-11-22 10:12:13

[2018湖南省队集训] 6.28 T3 simulate

这道模拟题出的我毫无脾气2333

最重要的是先要发现操作顺序不影响最后的答案,也就是每次随便挑一个>=2的数进行操作最后总是可以得到同样的数列。

(这个还不太难想qwq)

但是最骚的是接下来的模拟。。。。

我们考虑从左到右消,假设目前在i,1~i-1的已经都消成了0或1。

可以发现无非就是一下几种情况:

1.a[i]<2,不用管它

2.i==1,那么就 a[i+1]+=a[i]/2, a[i] &=1.

3.左边都是1,这样的话推一推会发现,可以将一轮视为 a[1] = 0,a[i]-- ,a[i+1]++

4.左边是1,推一推会发现这样相当于让 最近的一个0右移一位,然后a[i]--, a[i+1]++

5.左边是0,直接算,a[i-1]++,a[i]-=2,a[i+1]++,会减少一个0位置

如果我们用一个栈记录一下从左到右0的位置,那么就可以很方面的做上面的操作了。

接下来是非常炫酷的复杂度分析!

1操作的复杂度是O(N);

2操作的复杂度是 O(1);

3操作的最多次数不到初始所有a[]的和(因为每操作一次总和就--);

4操作可以优化成一次位移最大(也就是要么把a[i]减成<2的,要么把0移到i-1),如果移到i-1然后再结合5操作的话它的次数 = 5操作的次数;否则因为a[i]<1了,扫描线会右移。所以这一部分的总次数 <= 2*n + 3操作的次数。

5操作每次会让栈的大小-1,所以最多次数 <= 3操作的次数 + n。

于是这个算法的复杂度是O(N) 的(并且算复杂度很多地方都是取的极限的情况,所以实际跑起来飞快),非常的优秀 (雾

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=20000005; int a[N],n,s[N],tp;
char S[N]; int main(){
freopen("simulate.in","r",stdin);
freopen("simulate.out","w",stdout); scanf("%s",S+1),n=strlen(S+1);
for(int i=1;i<=n;i++) a[i]=S[i]-'0'; if(a[1]>=2) a[2]+=a[1]>>1,a[1]&=1;
if(!a[1]) s[++tp]=1; for(int i=2,L;i<=n;i++){
while(a[i]>=2)
if(!tp) a[i+1]++,a[i]--,s[++tp]=1,a[1]=0;
else if(s[tp]==i-1) a[i]-=2,a[s[tp]]=1,tp--,a[i+1]++;
else{
L=i-s[tp]-1;
if(a[i]<=L) a[i+1]+=a[i]-1,a[s[tp]]=1,s[tp]+=a[i]-1,a[s[tp]]=0,a[i]=1;
else a[i+1]+=L,a[s[tp]]=1,s[tp]+=L,a[s[tp]]=0,a[i]-=L;
} if(!a[i]) s[++tp]=i;
} for(int i=1;i<=n;i++) putchar(a[i]+'0');
return 0;
}