最近我重新复(学)习了一遍后缀自动机,发现原来有好多的东西已经全都忘掉了,这道题是我做kuangbin专题加强版时遇到的最难的一道题,也是到目前为止基本上是我写过的代码里最长的一个(树套树都没这么长啊QAQ),写了我整整一天
我做的专题是:打个广告,好像打不开,网址是 https://vjudge.net/contest/214869#overview
代码长度:压行以后,把debug的全删掉250行,加上就320行了,如果format一下就更长了,快400行了吧
时间:bzoj现在排名第4,2100ms,vjudge(spoj)上570ms,现在是第2,还是比较快的(orz rank1的大佬,好像是基本一样的方法,但是看都看不懂)
update:加了个输入挂,然后spoj->260ms,加了个输出挂,然后spoj->250ms,bzoj->1412ms,全都rank1了好爽啊,想要输入输出挂代码的可以去vjudge上找下,rank1感觉可能是由于机器升级了,这道题又没啥人做造成的
做法:主要参考了两个blog:blog1 blog2 还有这个blog3
虽然我由于我太菜,基本思路全都是参考的第二个blog,感觉写的都和第二个blog里的都差不多了,这里可以认为基本上是对那个blog的补充
题意我就不详细说了,主要是说:
两组串的set,S里是格式串,T串是目标串,求T中某串Ti在S中Si出现了几次
S串的增加方法是从一个已有的串往后加个字符,生成新串
T串的增加方法是从一个已有的串向前或者向后加个字符;或者两个已有的串连到一块生成新串
详细讲讲做法:(在线的我实在是不会啊,感觉怎么也不能直接做,或许后缀平衡树用替罪羊树均摊可做?感觉不太对的样子)
首先,我们知道一个定理,后缀自动机的fail树是反串的后缀树,然后我们利用这个定理来做这道题:
在线做的话,S串和T串都是会变化的,虽然S串变化不大,但是离线显然会简化问题:
然后我们就离线做了:
由于:第一,一个字符很简单就能找到在任意串出现的次数,第二,T串的增加方式和S串是一样的,所以我们只需要解决一个问题:两个串Ta和Tb,而且你知道Ta串在任意串中出现的次数和Tb串在任意串出现的次数,怎样维护加起来的串在任意串中才出现的次数呢?(这里维护任意串是因为如果只维护一个串或者某些串,我不知道怎样能让复杂度是对的)
这里我自己觉得只有hash一下才可以做,然后就GG了,但是其实有个如果从后缀树来反着看的话,有一些性质:
首先,如果将trie的串从小到大加到后缀自动机上的话,有个性质就是加入的点fail上长度是不一样的,也就是说从fail网往上看,每个点代表的串都不同,而且fail树上从0往下看每个开始的字符一定不同,否则就矛盾了
然后如果我们在trie上bfs,同时加进后缀自动机,就能够把trie按后缀排序了(这可能是个套路,然后我太菜并不知道)
那么我们对于T串的每一个串维护一个[L,R],表示作为一个串能在之前的后缀上的rank,然后合并时二分一下,然后在trie上求kth-father,就能够得出连起来的[L',R'] 了(二分然后将前串的rank和kth-father的比一比,这里的二分写的我好难受啊)
然后,由于这里求father比较多,或者说特别多,如果这里倍增来求kth-father,常数较大,而且多个log,坑爹的卡常(或者本意就是要卡这个log),导致过不了这道题。如果用32位的预处理是可过的,但是有更好的方法:kth-father有个利用长链剖分,O(nlogn)预处理,O(1)查询的ladder+倍增的方法,用这个方法就能过了(我写的时候还写挂了好长时间。。注意ladder只有top相等时要push),链接:zqc的blog
下面给下我的代码(为了清楚,不带输入输出挂)
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <cstring>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <utility>
#include <cassert>
using namespace std;
#define REP(I,N) for (I=0;I<N;I++)
#define rREP(I,N) for (I=N-1;I>=0;I--)
#define rep(I,S,N) for (I=S;I<N;I++)
#define rrep(I,S,N) for (I=N-1;I>=S;I--)
#define FOR(I,S,N) for (I=S;I<=N;I++)
#define rFOR(I,S,N) for (I=N;I>=S;I--)
typedef unsigned long long ULL;
typedef long long LL;
const int INF=0x3f3f3f3f;
const LL INFF=0x3f3f3f3f3f3f3f3fll;
const LL M=1e9+7;
const LL maxn=3e5+7;
const double eps=0.00000001;
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
template<typename T>inline T abs(T a) {return a>0?a:-a;}
template<typename T>inline T powMM(T a,T b){T ret=1;for (;b;b>>=1ll,a=1ll*a*a%M) if (b&1) ret=1ll*ret*a%M;return ret;}
//SPOJ COT4
struct SAM{
const static int maxn=2e5+7;
int next[maxn][26],fail[maxn],len[maxn],cnt,pos[maxn],Pos[maxn];
void init(){
cnt=0;fail[0]=-1;len[0]=0;pos[0]=0;
memset(next[0],0,sizeof(next[0]));
}
int add(int p,int c,int id){
int np=++cnt;pos[np]=Pos[np]=id;
memset(next[np],0,sizeof(next[np]));
len[np]=len[p]+1;
for (;p!=-1&&!next[p][c];p=fail[p]) next[p][c]=np;
if (p==-1) fail[np]=0;
else {
int q=next[p][c];
if (len[p]+1==len[q]) fail[np]=q;
else{
int nq=++cnt;len[nq]=len[p]+1;
memcpy(next[nq],next[q],sizeof(next[q]));
fail[nq]=fail[q];pos[nq]=pos[q];Pos[nq]=0;
fail[np]=fail[q]=nq;
for (;p!=-1&&next[p][c]==q;p=fail[p]) next[p][c]=nq;
}
}
return np;
}
int failnext[maxn][26];
};
struct TRIE{
SAM sam;
const static int maxn=1e5+26+7;
void init(){
sam.init();
tot=0;ToT=1;id[ToT]=0;val[0]=-1;//1:空
memset(next[0],0,sizeof(next[0]));
}
//1:trie上建树
int id[maxn],ToT;//queries
int next[maxn][26],last[maxn],tot;//on the trie
int val[maxn];
void Add(int i,int c){
int p=id[i];ToT++;
if (!next[p][c]) {
next[p][c]=++tot;val[tot]=c;
memset(next[tot],0,sizeof(next[tot]));
fa[tot][0]=p;
}id[ToT]=next[p][c];
}
int Q[maxn],st,ed;
void buildSAM(){
st=ed=0;Q[ed++]=0;
while (st!=ed){
int p=Q[st++];char c;
REP(c,26) if (next[p][c]){
int nxt=next[p][c];
last[nxt]=sam.add(last[p],c,nxt);
Q[ed++]=nxt;
}
}
}
//2:get L-R
int failtot;
int rank[maxn],sa[maxn];
void dfsrank(int x){
if (sam.Pos[x]) rank[sam.Pos[x]]=++failtot,sa[failtot]=sam.Pos[x];
char c;
REP(c,26) if (sam.failnext[x][c]) dfsrank(sam.failnext[x][c]);
}
void linkfail(){
int i;
memset(sam.failnext,0,sizeof(sam.failnext[0])*(sam.cnt+1));
FOR(i,1,sam.cnt)
sam.failnext[sam.fail[i]][val[prev(sam.pos[i],sam.len[sam.fail[i]])]]=i;
dfsrank(0);
}
//3:build_fa; ladder长链剖分
int fa[maxn][21],son[maxn],top[maxn],len[maxn],dep[maxn];
vector<int> ladder[maxn],upper[maxn];
int upp[maxn];
void buildfa(){
int i,j,c;
dep[0]=0;
FOR(i,1,tot) rep(j,1,21)
fa[i][j]=fa[fa[i][j-1]][j-1],dep[i]=dep[fa[i][0]]+1;
rFOR(i,0,tot){
int o=0;top[i]=i;
ladder[i].clear();
REP(c,26) if (next[i][c]){
int p=next[i][c];
if (!o||len[o]<len[p]) o=p;
}if (o) len[i]=len[o]+1;else o=0;
son[i]=o;top[i]=i;
}FOR(i,0,tot) if (son[i]) top[son[i]]=top[i];
rFOR(i,0,tot) ladder[top[i]].push_back(i);
FOR(i,1,tot) if (top[i]==i){
int u=i;
REP(j,len[i]){
u=fa[u][0];
ladder[i].push_back(u);
if (!u) break;
}
}upp[0]=-1;
FOR(i,1,tot) upp[i]=upp[i-1]+(i==(i&-i));
}
int prev(int x,int k){;
if (!k) return x;
x=fa[x][upp[k]];k-=1<<upp[k];
k-=dep[x]-dep[top[x]];x=top[x];
return ladder[x][len[x]+k];
}
}trie;
struct queries{
int op,i,j;char c;
}q[maxn];
//3:get Ans_L-R
int QAQ;
struct node{
int l,r,len,next;
node(){l=r=len=0;}
node(int _l,int _r,int _len):l(_l),r(_r),len(_len){};
}A[maxn],C[27];
node merge(node A,node B){//反着来的,B在后
if (A.len==0) return B;
if (B.len==0) return A;
if (B.l>B.r||A.l>A.r) return node(0,-1,A.len+B.len);
int l,r,L,R;
l=B.r+1;r=B.l-1;
for(L=B.l,R=B.r;L<=R;){
int mid=(L+R)/2;
if (trie.rank[trie.prev(trie.sa[mid],B.len)]<A.l) L=mid+1;
else R=mid-1,l=mid;
}
for(L=B.l,R=B.r;L<=R;){
int mid=(L+R)/2;
if (trie.rank[trie.prev(trie.sa[mid],B.len)]>A.r) R=mid-1;
else L=mid+1,r=mid;
}
return node(l,r,A.len+B.len);
}
//4:solve
int F[maxn];
inline int lowbit(int x){return x&-x;}
void update(int x,int val){
for (;x<=trie.failtot;x+=lowbit(x)) F[x]+=val;
}int query(int x){
int ret=0;
for (;x;x-=lowbit(x)) ret+=F[x];
return ret;
}int query(int l,int r){
if (l>r) return 0;
return query(r)-query(l-1);
}
node B[maxn];
int Ans[maxn],head[maxn];
void addnode(int x,int pos,int i){
x=trie.id[x];B[i]=A[pos];
B[i].next=head[x];head[x]=i;
}
void getans(int x){
int i;
if (x) update(trie.rank[x],1);
for (i=head[x];~i;i=B[i].next){
if (B[i].len&&B[i].l<=B[i].r) Ans[i]=query(B[i].l,B[i].r);
else Ans[i]=0;
}
REP(i,26) if (trie.next[x][i]) getans(trie.next[x][i]);
if (x) update(trie.rank[x],-1);
}
int n,m,Q;
int i,j,k;
char c;
int main(){
scanf("%d",&Q);
trie.init();
FOR(i,1,Q){
scanf("%d",&q[i].op);
if (q[i].op==1){
scanf("%d %c",&q[i].i,&c);q[i].c=c-'a';
trie.Add(q[i].i,q[i].c);
}else if (q[i].op==2){
scanf("%d%d %c",&q[i].i,&q[i].j,&c);q[i].c=c-'a';
}else scanf("%d%d",&q[i].i,&q[i].j);
}
trie.buildfa();
trie.buildSAM();
trie.linkfail();
REP(i,26){
int l,r,L,R;
l=trie.failtot+1;r=0;
for(L=1,R=trie.failtot;L<=R;){
int mid=(L+R)/2;
if (trie.val[trie.sa[mid]]<i) L=mid+1;
else R=mid-1,l=mid;
}
for(L=1,R=trie.failtot;L<=R;){
int mid=(L+R)/2;
if (trie.val[trie.sa[mid]]>i) R=mid-1;
else L=mid+1,r=mid;
}
C[i]=node(l,r,1);
}
FOR(i,0,trie.tot) head[i]=-1;QAQ=1;
FOR(i,1,Q){
if (q[i].op==1){
head[i]=-1;
}else if (q[i].op==2){
if (q[i].i==0) A[++QAQ]=merge(C[q[i].c],A[q[i].j]);
if (q[i].i==1) A[++QAQ]=merge(A[q[i].j],C[q[i].c]);
}else if (q[i].op==3)
A[++QAQ]=merge(A[q[i].i],A[q[i].j]);
else addnode(q[i].j,q[i].i,i);
}
getans(0);
FOR(i,1,Q) if (q[i].op==4) printf("%d\n",Ans[i]);
return 0;
}
/*
*/