AC自动机谜题
串
兔子们在玩字符串的游戏。首先,它们拿出了一个字符串集合 S,然后它们定义一个字符串为“好”的,当且仅当它可以被分成非空的两段,其中每一段都是字符串集合 S 中某个字符串的前缀。
比如对于字符串集合{"abc", "bca"},字符串"abb","abab"是“好”的("abb" = "ab"+"b", abab = "ab" + "ab"),而字符串“bc”不是“好”的。
兔子们想知道,一共有多少不同的“好”的字符串。
输入格式:
第一行一个整数 n,表示字符串集合中字符串的个数接下来每行一个字符串
输出格式:
一个整数,表示有多少不同的“好”的字符串样例输入:
2 ab ac
样例输出:
9
数据范围:
对于 20%的数据,1 <= n <= 200对于 50%的数据,1 <= n <= 2000
对于 100%的数据,1 <= n <= 10000,每个字符串非空且长度不超过 30,均为小写字母组成。
时间限制:
3s空间限制:
512m这是一道AC自动机的题.
我订题时,发现我的写法与大家的不一样.
别人是减去fail树上子树大小减一.
我是另一种写法.
对每个前缀找后缀相等的,长度比他短的另一前缀是否存在(即fail)
存在的话,ans-前缀集合中后缀为上文两前缀的前缀个数.
这竟然和fail子树大小等价!!!
玄学! 疑问尚未解决.
#include<cstdio> #include<cstring> #include<queue> #include<iostream> using namespace std; char a[1000010],s[1000010]; long long ans; int sz,n; int ch[1000010][26],f[1000010],v[1000010],val[1000010],fa[1000010],dep[1000100],tag[1000010]; void init(){ sz=0; ans=0; memset(ch[0],0,sizeof(ch[0])); memset(f,0,sizeof(f)); memset(v,0,sizeof(v)); } void insert(char *t,int p){ int x=0; for (int i=0; t[i]; i++){ int y=t[i]-'a'; if (!ch[x][y]){ ch[x][y]=++sz; dep[sz]=dep[x]+1; fa[sz]=x; memset(ch[sz],0,sizeof(ch[sz])); } x=ch[x][y]; } } void getfail(){ queue<int> q; f[0]=0; for (int i=0; i<=25; i++) if (ch[0][i]) q.push(ch[0][i]); while (!q.empty()){ int x=q.front(); q.pop(); for (int i=0; i<=25; i++){ int *y=&ch[x][i],*z=&ch[f[x]][i]; if (!(*y)){ (*y)=(*z); continue; } q.push(*y); f[*y]=(*z); } } } void re(int orz){ int x=orz; while (f[x]){ x=f[x]; int f=orz,t=dep[x]; while (t--) f=fa[f]; ++tag[f]; break;//? } } void calc(int orz){ int x=orz; while (f[x]){ x=f[x]; ans-=tag[x]; } } int main(){ scanf("%d",&n); init(); for (int i=1; i<=n; i++){ scanf("%s",a); insert(a,i); } ans=1ll*sz*sz; getfail(); // for (int i=1; i<=sz; ++i) cerr<<last[i]<<" "; cerr<<endl; // for (int i=1; i<=sz; ++i) cerr<<dep[i]<<" "; cerr<<endl; for (int i=1; i<=sz; ++i) re(i); // for (int i=1; i<=sz; ++i) cerr<<tag[i]<<" "; cerr<<endl; // for (int i=1; i<=sz; ++i) cerr<<val[i]<<" "; cerr<<endl; for (int i=1; i<=sz; ++i) calc(i); printf("%lld",ans); return 0; }
有待思考.