您的位置:首页 > 其它

bzoj2555 SubString

2017-02-13 20:48 253 查看

传送门

这**什么破题……

看题的时候突然发现:咦这题我怎么见过……后来想起来是sxysxy说给我的一道后缀自动机+LCT的破题,然后我偏不信这个邪,码了个分块hash试图水过去,然后发现不是TLE就是MLE……没办法,只能乖乖写正解了……

加入操作只会在原串末尾加一个字符串,不难想到维护当前串的后缀自动机,加入的时候直接逐个加入字符即可。询问的时候首先在后缀自动机上进行转移,转移到的那个点的right大小即为答案。而一个点的right大小就等于parent树上子树中不是因复制而产生的节点个数(似乎表达有点问题,凑合看吧……),而parent树的形态会发生变化,因此可以用LCT维护子树大小,实现的时候把这个点的父亲到根的值全部加上/减去这个子树的大小即可。

后缀自动机的更新均摊O(1),LCT单次操作均摊O(logn),总复杂度就是$O(|S|\log |S|+\sum{|T|}+Q\log |S|)$,反正跑得挺快就是了。

/**************************************************************
Problem: 2555
User: hzoier
Language: C++
Result: Accepted
Time:9748 ms
Memory:47112 kb
****************************************************************/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define isroot(x) ((x)->p==null||((x)->p->ch[0]!=(x)&&((x)->p->ch[1]!=(x))))
#define dir(x) ((x)==(x)->p->ch[1])
using namespace std;
const int maxn=1200010,maxm=3000010;
struct node{
int key,lazy;
node *ch[2],*p;
node(int d=0):key(d),lazy(0){}
inline void pushdown(){
if(!lazy)return;
key+=lazy;
ch[0]->lazy+=lazy;
ch[1]->lazy+=lazy;
lazy=0;
}
}null[maxn];
void decode(char*,int,int);
void expand(int);
int travel(const char*);
node *access(node*);
void link(node*,node*);
void cut(node*);
void splay(node*);
void rot(node*,int);
int root,last,cnt=0,val[maxn]={0},par[maxn]={0},go[maxn][2]={{0}};
char s[maxm],c[maxn];
int n,q,mask=0,ans;
int main(){
null->ch[0]=null->ch[1]=null->p=null;
root=last=++cnt;
null[root].ch[0]=null[root].ch[1]=null[root].p=null;
null[root].key=1;
scanf("%d%s",&q,s);
n=strlen(s);
for(int i=0;i<n;i++)expand(s[i]-'A');
while(q--){
scanf("%s%s",c,s);
decode(s,n=strlen(s),mask);
if(!strcmp(c,"ADD"))for(int i=0;i<n;i++)expand(s[i]-'A');
else{
printf("%d\n",ans=travel(s));
mask^=ans;
}
}
return 0;
}
void decode(char *s,int n,int mask){
for(int i=0;i<n;i++){
mask=(mask*131+i)%n;
swap(s[i],s[mask]);
}
}
void expand(int c){
int p=last,np=++cnt;
val[np]=val

+1; null[np].ch[0]=null[np].ch[1]=null[np].p=null; null[np].key=1; while(p&&!go[p][c]){ go[p][c]=np; p=par[p]; } if(!p){ par[np]=root; link(null+np,null+root); } else{ int q=go[p][c]; if(val[q]==val[p]+1){ par[np]=q; link(null+np,null+q); } else{ int nq=++cnt; val[nq]=val[p]+1; memcpy(go[nq],go[q],sizeof(go[q])); null[nq].ch[0]=null[nq].ch[1]=null[nq].p=null; par[nq]=par[q]; link(null+nq,null+par[q]); par[np]=par[q]=nq; link(null+np,null+nq); cut(null+q); link(null+q,null+nq); while(p&&go[p][c]==q){ go[p][c]=nq; p=par[p]; } } } last=np; } int travel(const char *c){ int x=root; while(*c)x=go[x][*c++-'A']; if(!x)return 0; splay(null+x); return null[x].key; } node *access(node *x){ node *y=null; while(x!=null){ splay(x); x->ch[1]=y; y=x; x=x->p; } return y; } void link(node *x,node *y){ splay(x); x->p=y; access(y)->lazy+=x->key; } void cut(node *x){ access(x); splay(x); x->ch[0]->lazy-=x->key; x->ch[0]->p=null; x->ch[0]=null; } void splay(node *x){ x->pushdown(); while(!isroot(x)){ if(!isroot(x->p))x->p->p->pushdown(); x->p->pushdown(); x->pushdown(); if(isroot(x->p)){ rot(x->p,dir(x)^1); break; } if(dir(x)==dir(x->p))rot(x->p->p,dir(x->p)^1); else rot(x->p,dir(x)^1); rot(x->p,dir(x)^1); } } void rot(node *x,int d){ node *y=x->ch[d^1]; if((x->ch[d^1]=y->ch[d])!=null)y->ch[d]->p=x; y->p=x->p; if(!isroot(x))x->p->ch[dir(x)]=y; (y->ch[d]=x)->p=y; } /* 操作都是向后添加字符,因此考虑后缀自动机。 又因为需要动态维护子树大小,因此用LCT维护。 */

View Code [p]其实还有平衡树维护dfs序列的做法,然而以前没写过没敢写……

还有两个个脑洞级别的做法(虽然分块hash不知为何过不去……):

分块hash,设定一个阈值M,把原串所有长度<=M的子串的hash值扔到hash表里,询问时对于长度>M的串直接暴力KMP或者扫一遍原串的hash值,长度<=M的串扔到hash表里询问一下即可,取$M=\sqrt{\sum{|T|}}$可以达到理论最优复杂度$O(|S|\sqrt{\sum{|T|}} +\sum{|T|}+Q)$,然而用自然溢出hash会被卡,对998244353取模的话就会TLE/MLE……卡我分块,出题人**

既然可以后缀自动机+LCT,总不能不给后缀数组留条活路吧,所以后缀平衡树出场了。把原串反过来,每次添加一个字符就是在反串的前面添加一个字符,用二分+hash或者别的什么办法就可以快速比较两个反串后缀的大小,询问的时候在后缀平衡树上随便搞搞就行了,复杂度应该是$O(\log^2|S|)$,因此如果用二分hash比较后缀大小的话总复杂度应该是$O(|S|\log^2|S|+\sum{|T|}+Q\log^2|S|)$,看上去不错,然而我没写过后缀平衡树,这个做法也就只能留作脑洞了……

调了半天的原因居然又是把splay写错了……这人没救了

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: