(我还能怎样,能怎样,最后还不是像父亲一样把你原谅 ——鲁迅)
【简化题意】
给一个长度小于k的字符串和n个操作。
保证操作后字符串长度大于k,求最后的字符串中前k个字符。
操作规则如下:
给定一个(伪)区间[l,r],将区间中编号为奇数的字符形成一个新字符串,编号为偶数的字符形成一个字符串,将两个字符串拼接起来并放在编号为r字符的后边。
n<=5000,k<=3e6
【分析】
传统的思路上,不难发现我们只需要关心前k个字符即可。不需要多余的部分,然后就开心地水到了70分。
观察发现,超时的原因是因为我们产生了很多冗余的字符串,而这些字符串最终对答案并没有什么影响,产生了大量编号大于k的垃圾。
现在我们要避免产生垃圾,可以倒着往前做,求解密后的字符串第k位,和之前第几位是相同的,最终追溯到密文的那几位。再快速还原。先不求出具体的值,而是求出的问题中的拓扑关系。最终利用这个拓扑关系,快速还原出正解。
显然倒着做之后,确认过拓扑关系的点(也就是新产生的点)不会出现在上一步的字符串当中,所以要删除(或忽略),但是删除之后要重新拼接各个字符串并重新标号非常费时,所以我们考虑利用线段树来统计“在第i到第k位时有多少个数还未删除”,显然未删除的数就是当前状态下的编号。
【code】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3e6+100;
struct Tree{
int l,r,val;
}t[maxn<<2];
int l[maxn],r[maxn],qian[maxn];
char s[maxn],ans[maxn];
int n,k,p,q;
inline void read(int &x){
x=0;int fl=1;char tmp=getchar();
while(tmp<'0'||tmp>'9'){if(tmp=='-')fl=-fl;tmp=getchar();}
while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
x=x*fl;
}
void build(int x,int l,int r){
t[x].l=l,t[x].r=r,t[x].val=r-l+1;
if(l==r) return ;
int mid=l+r>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
int change(int x,int l,int r,int y,int z){
int mid;
while(l<r){
t[x].val-=z;
mid=l+r>>1;
if(t[x<<1].val<y)l=mid+1,y-=t[x<<1].val,x=(x<<1)+1;else r=mid,x=x<<1;
}
t[x].val-=z;
return l;
}
int main(){
freopen("father.in","r",stdin);
freopen("father.out","w",stdout);
scanf("%s",s+1);
scanf("%d%d",&k,&n);
for(int i=1;i<=n;i++)
read(l[i]),read(r[i]);
build(1,1,k);
for(int j=n;j>=1;j--){
if(r[j]>=t[1].val) continue;
if(l[j]&1) p=l[j];
else if(l[j]+1<=r[j]) p=l[j]+1;
else p=l[j];
for(int i=1;i<=r[j]-l[j]+1;i++){
if(r[j]>=t[1].val) continue;
q=change(1,1,k,r[j]+1,1);
qian[q]=change(1,1,k,p,0);
p+=2;
if(p>r[j]){
if(l[j]%2)p=l[j]+1;else p=l[j];
}
}
}
p=0;
for(int i=1;i<=k;i++){
if(qian[i])ans[i]=ans[qian[i]];else ans[i]=s[++p];
putchar(ans[i]);
}
}