[bzoj十连测第五场 B]可持久化字符串

题目大意

一个S的循环节T表示为可以找到一个正整数k使得S是 Tk 的前缀。
一次操作会在字符串尾部添加一个字符,并且你需要在每次操作后输出最小循环节长度。
要求可持久化与在线。

只跳log步

我们知道结论,答案就是i-f[i]。
如何可持久化KMP?
我们考虑一种做法让一次更新KMP只需要跳log步。
具体的,如果目前在j,我们看f[j]+1是否与i匹配。
如果匹配,那么f[i]=f[j]+1。
如果不匹配的话,我们分类讨论一下:
2*f[j]<=j,直接让j=f[j]。
2*f[j]>j,此时长度为j的前缀一定是一个循环串,存在长度为j-f[j]的循环节,而且显然这个 jf[j]<j/2
那么我们知道了每个循环段中,f[j]+1所对应的位置都一样的,没必要一步一步调整过去,直接让j=j%(j-f[j])跳到第一段去即可。
可以观察到,每一次跳我们至少让j缩小了一半,所以最多log步。
要注意,如果j跳到0,应该特判第一位是否匹配。
这题需要可持久化,那么字符串形成一个树的形状,为了找到根到一个叶子所代表字符串的一个位置,我们需要倍增。
于是复杂度上限是两个log的,而其实远远达不到上限。

#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=300000+10;
int fa[maxn][25],d[maxn],f[maxn],a[maxn];
int i,j,k,l,t,n,m,tot,ans,x,y;
bool czy;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int get(int x,int y){
    int j=floor(log(n)/log(2));
    while (j>=0){
        if (d[fa[x][j]]>=y) x=fa[x][j];
        j--;
    }
    return x;
}
int main(){
    freopen("string.in","r",stdin);freopen("string.out","w",stdout);
    n=read();m=read();czy=read();
    fo(i,1,n){
        x=read();y=read();
        if (czy) x^=ans,y^=ans;
        fa[++tot][0]=x;
        d[tot]=d[x]+1;
        a[tot]=y;
        fo(j,1,floor(log(n)/log(2)))
            fa[tot][j]=fa[fa[tot][j-1]][j-1];
        if (d[tot]>1){
            j=fa[tot][0];
            while (j){
                k=get(tot,d[f[j]]+1);
                if (a[k]==a[tot]) break;
                k=0;
                if (2*d[f[j]]<=d[j]) j=f[j];
                else j=get(tot,d[j]%(d[j]-d[f[j]]));
            }
            if (!k)
                if (a[get(tot,1)]==a[tot]) k=a[get(tot,1)];
            f[tot]=k;
        }
        ans=d[tot]-d[f[tot]];
        printf("%d\n",ans);
    }
}

你可能感兴趣的:(树上倍增,KMP)