您的位置:首页 > 其它

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

2016-07-14 09:50 197 查看

题目大意

一个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]的循环节,而且显然这个j−f[j]<j/2。

那么我们知道了每个循环段中,f[j]+1所对应的位置都一样的,没必要一步一步调整过去,直接让j=j%(j-f[j])跳到第一段去即可。

可以观察到,每一次跳我们至少让j缩小了一半,所以最多log步。

要注意,如果j跳到0,应该特判第一位是否匹配。

这题需要可持久化,那么字符串形成一个树的形状,为了找到根到一个叶子所代表字符串的一个位置,我们需要倍增。

于是复杂度上限是两个log的,而其实远远达不到上限。

#include<cstdio>
#include<cmath>
#include<algorithm>
#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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: