[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;
}

  

posted @ 2018-06-29 17:29  蒟蒻JHY  阅读(511)  评论(1编辑  收藏  举报