[codeforces217E]Alien DNA

题目大意

给你一个字符串和n个操作,每个操作用li,ri表示:把当前字符串[li,ri]区间取出,把取出的字符串重新1–r-l+1编号,然后编号为奇数的按顺序写下来,再把编号为偶数的按顺序写下来,把得到的新字符串加在区间后面。

例如:
s1s2s3s4s5s6 l=2 r=4 操作后的字符串变成s1s2s3s4s3s2s4s5s6

最后输出前k个字符。

k≤3000000 n≤5000

分析

正着做显然很难,可以考虑倒过来做。
只考虑前k个位置。首先对于最后一个操作,如果r≥k,那么复制出来的区间一定不会被输出,就可以跳过了。
如果r < k,可以很容易求出复制出区间[r+1,2r-l+1]与区间[1,k]的交集。然后就把字符复制过来。假设交集长度为x,问题变成剩下n-1个操作对剩下k-x个位置进行复制了。

查询位置

现在问题在于:如何快速得出一个位置在当前[1,k]区间中的位置(因为区间中一些位置可能被占用了)。由于现在是倒过来做的,已经确定的位置不会被修改。
很显然最多复制k次,那么每次找位置用线段树,可用的位置赋值为1,被占用的为0即可。
时间复杂度 O(klogk)
有人3000000的线段树卡过去了。但是有另一种方法:
区间是连续的,假如我已经知道x在区间对应的位置了,现在要求x+1的位置,可以用并查集来跑。
现在问题就是如何确定第一个位置。假设当前做到第i个操作,把第i+1——n个操作覆盖的区间存下来,然后按位置从左到右顺序枚举一遍即可(注意一个操作可能被分成几部分,那么把它与把它分开的区间合并)。
时间复杂度 O(n2+kαk)

#include <cstdio> 
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=3000005,maxm=5005;

int n,m,tot,f[maxn],fa[maxn],l[maxm],r[maxm],X[maxm],Y[maxm],a[maxm],len,seq[maxn];

char S[maxn],c;

int get(int x)
{
    int l=1;
    seq[1]=x;
    for (;f[seq[l]];l++) seq[l+1]=f[seq[l]];
    for (int i=1;i<l;i++) f[seq[i]]=seq[l];
    return seq[l];
}

int getp(int x)
{
    if (!tot) return x;
    int i=1,last;
    if (x<X[a[i]]) return x;
    x-=X[a[i]]-1;
    for (last=i++;i<=tot;i++) if (X[a[i]]>Y[a[last]])
    {
        if (x>X[a[i]]-Y[a[last]]-1)
        {
            x-=X[a[i]]-Y[a[last]]-1;
            last=i;
        }else return Y[a[last]]+x;
    }
    return Y[a[last]]+x;
}

int main()
{
    //freopen("father.in","r",stdin); freopen("father.out","w",stdout);
    scanf("%s%d%d",S+1,&m,&n);
    for (int i=0;i<n;i++) scanf("%d%d",&l[i],&r[i]);
    memset(f,0,sizeof(f));
    memset(fa,0,sizeof(fa));
    for (int i=n-1;i>=0;i--) if (r[i]<m)
    {
        int p=getp(r[i]+1);
        if (p<=m)
        {
            int k=l[i]+1;
            if (k>r[i]) k=l[i];
            int P=getp(k),lim=(r[i]<<1)-l[i]+1,j;
            X[i]=p;
            for (j=r[i]+1;j<=lim && p<=m;j++,p=get(p))
            {
                fa[p]=P;
                f[p]=p+1;
                k+=2;
                if (k<=r[i]) P=get(get(P+1)+1);else
                {
                    k=l[i];
                    P=getp(k);
                }
                Y[i]=p;
            }
            for (j=tot;j && X[a[j]]>X[i];j--) a[j+1]=a[j];
            a[j+1]=i; tot++;
        }
    }
    int cnt=0;
    for (int i=1;i<=m;i++)
    {
        fa[i]=(!fa[i])?++cnt:fa[fa[i]];
        printf("%c",S[fa[i]]);
    }
    printf("\n");
    //fclose(stdin); fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值