题意:
给你一个字符串,n个操作 [li,ri] [ l i , r i ] ,每次操作把区间内的字符串复制一遍并打乱接在后面,打乱的规则为:
s1s2s3...sn−>s2s4s6...s1s3s5... s 1 s 2 s 3 . . . s n − > s 2 s 4 s 6 . . . s 1 s 3 s 5 . . . ,求n次操作后结果的前k个字符
Solution:
这道题正着去考虑不是很好想,我们尝试反着来思考:假设现在得到了结果串,我们把它进行还原,显然还原的时候字符串长度会越来越短,所以我们只需考虑前k个即可,每次还原的时候,我们可以确定复制区间 [r+1,2r−l+1]∩[1,k] [ r + 1 , 2 r − l + 1 ] ∩ [ 1 , k ] 与原串的对应关系,然后把这个复制区间删去,类似这样一步一步推上去就可以了
最后从前往后扫一遍,如果遇到没有对应关系的位置就说眀这个位置是最初给的串中的字符
倒推过程可以通过平衡树或者线段树,一开始写了平衡树结果T了…后来改成了线段树
因为最多被删除k次,所以总复杂度 O(klogk) O ( k log k )
代码;
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
char s[3000010];
char f[3000010];
int ans[3000010];
int root=0;
struct tree{
int v,l,r;
}tr[4*3000010];
int n,l[5010],r[5010],k,size;
void build(int i,int l,int r)
{
tr[i].l=l;tr[i].r=r;
tr[i].v=r-l+1;
if (l==r) return;
int mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
}
int rk(int i,int k)
{
if (tr[i].l==tr[i].r) return tr[i].l;
if (tr[i<<1].v>=k) return rk(i<<1,k);
else return rk(i<<1|1,k-tr[i<<1].v);
}
void del(int i,int k)
{
tr[i].v--;
if (tr[i].l==tr[i].r) return;
int mid=tr[i].l+tr[i].r>>1;
if (k<=mid) del(i<<1,k);
else del(i<<1|1,k);
}
int main()
{
scanf("%s",s+1);
scanf("%d%d",&k,&n);
for (int i=1;i<=n;i++) scanf("%d%d",&l[i],&r[i]);
int rst=k;
build(1,1,k);
for (int i=n;i>=1;i--)
{
if (r[i]>=rst) continue;
int cnt=0;
for (int j=l[i];j<=r[i];j++)
{
int L=j+r[i]-l[i]+1;
if (L>rst) break;
cnt++;
int nw=rk(1,r[i]+1);
int J;
int p=j-l[i]+1-(r[i]-l[i]+1)/2;
if (p>0) J=l[i]+p*2-2;
else J=l[i]+(j-l[i]+1)*2-1;
int pre=rk(1,J);
ans[nw]=pre;
//cout<<L<<" "<<J<<" "<<nw<<" "<<pre<<endl;
del(1,nw);
}
rst-=cnt;
}
int nw=0;
for (int i=1;i<=k;i++)
if (ans[i]==0) f[i]=s[++nw];
else f[i]=f[ans[i]];
printf("%s",f+1);
}