BZOJ3172 [Tjoi2013]单词 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9026543.html
题目传送门 - BZOJ3172
题意
输入$n(n\leq 200)$个字符串,保证长度总和$\leq 10^6$。
对于每一个字符串,求它在所有的$n$个字符串(包括它自己)中出现了几次。(同一个字符串内可能出现多次当前的字符串)
题解
听百度说这题可以用AC自动机??
然而我顺手大力$SA$干掉了此题。
本题用$SA$做好像很容易。
首先,把输入的字符串连接成一个字符串,不同的单词之间用特殊字符隔开。注意最终串长度是$1e6+n$级别的,数组别开小。
然后闭着眼睛$SA$啊。
记第$i$个串的开头为$p_i$,长度为$len_i$。
对于第$i$个串,就是从$rank_{p_i}$位置开始。向左,利用$height$表示的$lcp$长度,如果$lcp$长度不小于$len_i$,那么仍然可以继续向左扩张。向右同理。
这样好像就可以过掉了。
然而我却在搜百度前就写完了$ST$表……好像快了不少。
上一句为题外话。
向左向右一步一步扩张显然太慢,所以我们预处理$height$的$ST$表,然后倍增地走。每次走的复杂度为$O(\log m)$。
总复杂度$O((n+m)\log m)$。
代码
#include <bits/stdc++.h> using namespace std; const int N=205,M=1000205; int n,m=0,p[N],len[N]; int SA[M],rank[M],height[M],tmp[M],tax[M]; int ST[M][22]; char s[M]; void Sort(int n,int m){ for (int i=0;i<=m;i++) tax[i]=0; for (int i=1;i<=n;i++) tax[rank[i]]++; for (int i=1;i<=m;i++) tax[i]+=tax[i-1]; for (int i=n;i>=1;i--) SA[tax[rank[tmp[i]]]--]=tmp[i]; } bool cmp(int rk[],int x,int y,int w){ return rk[x]==rk[y]&&rk[x+w]==rk[y+w]; } void Suffix_Array(char s[],int n){ memset(SA,0,sizeof SA); memset(tmp,0,sizeof tmp); memset(rank,0,sizeof rank); memset(height,0,sizeof height); for (int i=1;i<=n;i++) rank[i]=s[i],tmp[i]=i; int m=127; Sort(n,m); for (int w=1,p=0;p<n;w<<=1,m=p){ p=0; for (int i=n-w+1;i<=n;i++) tmp[++p]=i; for (int i=1;i<=n;i++) if (SA[i]>w) tmp[++p]=SA[i]-w; Sort(n,m); swap(rank,tmp); rank[SA[1]]=p=1; for (int i=2;i<=n;i++) rank[SA[i]]=cmp(tmp,SA[i],SA[i-1],w)?p:++p; } for (int i=1,j,k=0;i<=n;height[rank[i++]]=k) for (k=max(k-1,0),j=SA[rank[i]-1];s[i+k]==s[j+k];k++); } void Get_ST(int n){ memset(ST,0,sizeof ST); for (int i=1;i<=n;i++){ ST[i][0]=height[i]; for (int j=1;j<22;j++){ ST[i][j]=ST[i][j-1]; if (i-(1<<(j-1))>0) ST[i][j]=min(ST[i][j],ST[i-(1<<(j-1))][j-1]); } } } int main(){ scanf("%d",&n); for (int i=1;i<=n;i++){ scanf("%s",s+m+1); p[i]=m+1,len[i]=strlen(s+m+1); m+=len[i]+1; s[m]='#'; } Suffix_Array(s,m); Get_ST(m); for (int i=1;i<=n;i++){ int ans=1,pos=rank[p[i]],L=len[i]; for (int j=21,now=pos;j>=0;j--) if (now-(1<<j)>0&&ST[now][j]>=L) now-=1<<j,ans+=1<<j; for (int j=21,now=pos;j>=0;j--) if (now+(1<<j)<=m&&ST[now+(1<<j)][j]>=L) now+=1<<j,ans+=1<<j; printf("%d\n",ans); } return 0; }