【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
2015-12-29 22:20
495 查看
据说后缀自动机可以替代后缀数组和后缀树……
后缀自动机,用线性的节点数来保存所有的后缀。
构建自动机
后缀自动机有几条性质。
1.某两个节点u、v(len(v)<=len(u))是终点等价类当且仅当v为u的后缀。
2.link指针组成了一棵以root(root为虚拟节点)的树。儿子的终点集合是父亲的终点集合的子集。
3.某一个节点的终点集合元素个数是它的子树节点个数(以后缀边link组成的树)。若该节点不是原字符串的后缀,则减一。
其中,如何计算终点集合元素个数是后缀自动机的核心所在。
SPOJ - LCS
题目大意:给你两个字符串,求这两个字符串的最长公共子串(子串是连续的)。
首先对第一个字符串构建后缀数组,再把第二个字符串带入。
SPOJNSUBSTR
题目大意:对一个给定字符串s,求F(i)(1<=i<=len(s))。其中F(i)表示长度为i的子串出现的最多次数。
首先对字符串s构建一个后缀自动机。之后遍历由后缀边组成的树。
用F(i+1)去更新F(i)。
注意:把MAXN开大一倍……
SPOJ-LCS2
这道题是SPOJ-LCS的扩展版,即求多个字符串的最长公共子串。
在SPOJ-LCS中加上一个变量,跟着字符串更新即可。
然而蒟蒻TLE了几次,原因是木有把标记清零= =
HDU4416
题目大意:求是A串的子串但是不是B串的子串的个数。
后缀自动机的一个经典运用。
对A构建自动机,把B串代入自动机进行匹配,记录最长匹配长度。最后统计答案即可。具体步骤如下:
1.对于当前节点u,用ans[u]更新ans[tree[u].link]
2.如果tree[u].len>ans[u],说明在节点u中,长度[ans[u]+1,tree[u].len]的子串不是B串的子串。
3.如果ans[u]==0,说明节点u中的子串均满足条件,但节点u中的子串为tree[u].len−tree[tree[u].link].len
这样这道题就简单了许多,而且实现细节上木有什么坑点。
后缀自动机,用线性的节点数来保存所有的后缀。
构建自动机
struct node { int ch[26], len, link; void init() { len=link=0; memset(ch,0,sizeof ch); } }tree[MAXN<<1]; int pos; char w[MAXN]; void add(char v) { int now=++pos; v-='a'; tree[now].len=tree[last].len+1; tree[last].ch[v]=now; int p; for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link) tree[p].ch[v]=now; if(!p)tree[now].link=1; else { int q=tree[p].ch[v]; if(tree[p].len+1==tree[q].len) tree[now].link=q; else { tree[++pos]=tree[q]; tree[pos].len=tree[p].len+1; tree[now].link=tree[q].link=pos; while(p&&tree[p].link==q) { tree[p].ch[v]=pos; p=tree[p].link; } } } last=now; } void build(char a[]) { for(int i=1;i<=pos;++i)tree[i].init(); int len=strlen(a); last=pos=1; for(int i=0;i<len;++i) add(a[i]); }
后缀自动机有几条性质。
1.某两个节点u、v(len(v)<=len(u))是终点等价类当且仅当v为u的后缀。
2.link指针组成了一棵以root(root为虚拟节点)的树。儿子的终点集合是父亲的终点集合的子集。
3.某一个节点的终点集合元素个数是它的子树节点个数(以后缀边link组成的树)。若该节点不是原字符串的后缀,则减一。
其中,如何计算终点集合元素个数是后缀自动机的核心所在。
SPOJ - LCS
题目大意:给你两个字符串,求这两个字符串的最长公共子串(子串是连续的)。
首先对第一个字符串构建后缀数组,再把第二个字符串带入。
#include <iostream> #include <cstdio> #include <cstring> #define MAXN 250005 using namespace std; char a[MAXN], b[MAXN]; int ans, tmp, last, cur, cnt, root; struct node { int link, len, ch[26]; void init() { link=len=0; memset(ch,0,sizeof ch); } }tree[MAXN<<1]; void add(char a) { int p=0, v=a-'a'; tree[last].ch[v]=++cnt; tree[cnt].len=tree[last].len+1; cur=cnt; for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)tree[p].ch[v]=cur; if(!p)tree[cur].link=root; else { int q=tree[p].ch[v]; if(tree[p].len+1==tree[q].len)tree[cur].link=q; else { tree[++cnt]=tree[q]; tree[cnt].len=tree[p].len+1; tree[cur].link=tree[q].link=cnt; while(p&&tree[p].ch[v]==q) { tree[p].ch[v]=cnt; p=tree[p].link; } } } last=cur; } void build(char a[]) { for(int i=1;i<=cnt;++i)tree[i].init(); cnt=last=cur=root=1; int len=strlen(a); for(int i=0;i<len;++i) add(a[i]); } int main() { int len, p, v; while(~scanf("%s%s",a,b)) { build(a); len=strlen(b); ans=tmp=0, p=root; for(int i=0;i<len;++i) { v=b[i]-'a'; if(tree[p].ch[v]) { p=tree[p].ch[v]; ++tmp; } else { while(p&&!tree[p].ch[v]) p=tree[p].link; if(!p) p=root, tmp=0; else tmp=tree[p].len+1, p=tree[p].ch[v]; } ans=max(ans,tmp); } printf("%d\n",ans); } return 0; }
SPOJNSUBSTR
题目大意:对一个给定字符串s,求F(i)(1<=i<=len(s))。其中F(i)表示长度为i的子串出现的最多次数。
首先对字符串s构建一个后缀自动机。之后遍历由后缀边组成的树。
用F(i+1)去更新F(i)。
注意:把MAXN开大一倍……
#include <iostream> #include <cstdio> #include <cstring> #define MAXN 500005 using namespace std; struct node { int link, ch[26], len, cnt; void init() { len=link=0; memset(ch,0,sizeof ch); } }tree[MAXN<<1]; char w[MAXN]; int last, pos; int cnt[MAXN], F[MAXN], len, r[MAXN]; void add(char a) { int now=++pos, v=a-'a', p; tree[last].ch[v]=now; tree[now].len=tree[last].len+1; for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link) tree[p].ch[v]=now; if(!p)tree[now].link=1; else { int q=tree[p].ch[v]; if(tree[p].len+1==tree[q].len)tree[now].link=q; else { tree[++pos]=tree[q]; tree[pos].len=tree[p].len+1; tree[now].link=tree[q].link=pos; while(p&&tree[p].ch[v]==q) { tree[p].ch[v]=pos; p=tree[p].link; } } } last=now; } void build(char a[]) { for(int i=1;i<=pos;++i)tree[i].init(); last=pos=1; for(int i=0;i<len;++i) add(a[i]); } int main() { scanf("%s",w); len=strlen(w); build(w); for(int i=1;i<=pos;++i)++cnt[tree[i].len]; for(int i=1;i<=len;++i)cnt[i]+=cnt[i-1]; for(int i=1;i<=pos;++i)r[cnt[tree[i].len]--]=i; int p=1; for(int i=0;i<len;++i) p=tree[p].ch[w[i]-'a'], ++tree[p].cnt; int tmp; for(int i=pos;i>0;--i) { tmp=r[i]; F[tree[tmp].len]=max(F[tree[tmp].len],tree[tmp].cnt); if(tree[tmp].link)tree[tree[tmp].link].cnt+=tree[tmp].cnt; } for(int i=len-1;i>0;--i)F[i]=max(F[i],F[i+1]); for(int i=1;i<=len;++i)printf("%d\n",F[i]); return 0; }
SPOJ-LCS2
这道题是SPOJ-LCS的扩展版,即求多个字符串的最长公共子串。
在SPOJ-LCS中加上一个变量,跟着字符串更新即可。
然而蒟蒻TLE了几次,原因是木有把标记清零= =
#include <iostream> #include <cstdio> #include <cstring> #define MAXN 200005 #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) using namespace std; struct node { int ch[26], len, link; void init() { len=link=0; memset(ch,0,sizeof ch); } }tree[MAXN<<1]; int pos, last, ans[MAXN<<1], cnt[MAXN], r[MAXN], temp[MAXN<<1]; char w[MAXN]; void add(int v) { int now=++pos, p; tree[last].ch[v]=now; tree[now].len=tree[last].len+1; for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link) tree[p].ch[v]=now; if(!p)tree[now].link=1; else { int q=tree[p].ch[v]; if(tree[p].len+1==tree[q].len)tree[now].link=q; else { tree[++pos]=tree[q]; tree[pos].len=tree[p].len+1; tree[now].link=tree[q].link=pos; while(p&&tree[p].ch[v]==q) { tree[p].ch[v]=pos; p=tree[p].link; } } } last=now; } void build(char a[]) { for(int i=1;i<=pos;++i)tree[i].init(); int len=strlen(a); last=pos=1; for(int i=0;i<len;++i) add(a[i]-'a'); } int main() { scanf("%s",w); build(w); int len, v, p, tmp; len=strlen(w); for(int i=1;i<=pos;++i)++cnt[tree[i].len]; for(int i=1;i<=len;++i)cnt[i]+=cnt[i-1]; for(int i=pos;i>0;--i)r[cnt[tree[i].len]--]=i; for(int i=1;i<=pos;++i)ans[i]=tree[i].len; while(~scanf("%s",w)) { len=strlen(w); p=1, tmp=0; for(int i=0;i<len;++i) { v=w[i]-'a'; if(tree[p].ch[v]) p=tree[p].ch[v], ++tmp; else { while(p&&!tree[p].ch[v])p=tree[p].link; if(!p)p=1, tmp=0; else tmp=tree[p].len+1, p=tree[p].ch[v]; } temp[p]=max(temp[p],tmp); } for(int i=pos;i>0;--i) { v=r[i]; ans[v]=min(ans[v],temp[v]); if(tree[v].link&&temp[v]) { int q=tree[v].link; temp[q]=min(tree[q].len,max(temp[q],temp[v])); } temp[v]=0;//清空!!! } } int out=0; for(int i=1;i<=pos;++i)out=max(out,ans[i]); printf("%d\n",out); return 0; }
HDU4416
题目大意:求是A串的子串但是不是B串的子串的个数。
后缀自动机的一个经典运用。
对A构建自动机,把B串代入自动机进行匹配,记录最长匹配长度。最后统计答案即可。具体步骤如下:
1.对于当前节点u,用ans[u]更新ans[tree[u].link]
2.如果tree[u].len>ans[u],说明在节点u中,长度[ans[u]+1,tree[u].len]的子串不是B串的子串。
3.如果ans[u]==0,说明节点u中的子串均满足条件,但节点u中的子串为tree[u].len−tree[tree[u].link].len
这样这道题就简单了许多,而且实现细节上木有什么坑点。
#include <iostream> #include <cstdio> #include <cstring> #define MAXN 100005 #define LL long long int #define max(a,b) ((a)>(b)?(a):(b)) using namespace std; char w[MAXN]; int n, cnt, last, len; int c[MAXN], q[MAXN<<1], ans[MAXN<<1]; struct node { int len, ch[26], link; void init() { len=link=0; memset(ch,0,sizeof ch); } }tree[MAXN<<1]; void insert(int v) { int now=++cnt; tree[now].len=tree[last].len+1; tree[last].ch[v]=now; int p; for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link) tree[p].ch[v]=now; if(!p)tree[now].link=1; else { int q=tree[p].ch[v]; if(tree[q].len==tree[p].len+1)tree[now].link=q; else { int tmp=++cnt; tree[tmp]=tree[q]; tree[tmp].len=tree[p].len+1; tree[now].link=tree[q].link=tmp; while(p&&tree[p].ch[v]==q) { tree[p].ch[v]=tmp; p=tree[p].link; } } } last=now; } void build(char w[]) { for(int i=0;i<=cnt;++i)tree[i].init(), ans[i]=0; last=cnt=1; len=strlen(w); for(int i=0;i<len;++i)insert(w[i]-'a'); memset(c,0,sizeof c); for(int i=1;i<=cnt;++i)++c[tree[i].len]; for(int i=1;i<=len;++i)c[i]+=c[i-1]; for(int i=1;i<=cnt;++i)q[c[tree[i].len]--]=i; } void query(char w[]) { int len=strlen(w), p=1, v, tmp=0; for(int i=0;i<len;++i) { v=w[i]-'a'; if(tree[p].ch[v]) { ++tmp; p=tree[p].ch[v]; } else { while(p&&!tree[p].ch[v])p=tree[p].link; if(!p)p=1, tmp=0; else{tmp=tree[p].len+1;p=tree[p].ch[v];} } ans[p]=max(ans[p],tmp); } } LL solve() { LL tot=0; int v; for(int i=cnt;i>0;--i) { v=q[i]; if(ans[v]) { ans[tree[v].link]=max(ans[tree[v].link],ans[v]); if(tree[v].len>ans[v])tot+=tree[v].len-ans[v]; } else tot+=tree[v].len-tree[tree[v].link].len; } return tot; } int main() { int cas, CNT=0; scanf("%d",&cas); while(cas--) { scanf("%d%s",&n,w); build(w); while(n--) { scanf("%s",w); query(w); } printf("Case %d: %I64d\n",++CNT,solve()); } return 0; }
相关文章推荐
- bzoj 2806: [Ctsc2012]Cheat
- SPOJ NSUBSTR
- bzoj-2780 Sevenk Love Oimaster
- bzoj-2555 SubString
- CF235C Cyclical Quest
- 【Vijos1382】【BZOJ1398】寻找主人 Necklace
- 【ZJOI2015】【BZOJ3926】诸神眷顾的幻想乡
- SPOJ 8222. Substrings(后缀自动机模板)
- HDU 4436 str2int 后缀数组 + 前缀和预处理 或 后缀自动机
- SPOJ LCS Longest Common Substring 后缀自动机
- SPOJ LCS2 Longest Common Substring II 后缀自动机
- SPOJ SUBLEX Lexicographical Substring Search 后缀自动机
- SPOJ NSUBSTR Substrings 后缀自动机
- POJ 1509 Glass Beads 后缀自动机 或 后缀数组
- Codeforces 235C Cyclical Quest 后缀自动机
- HDU 4641 K-string 2013年多校第4场J题 后缀自动机
- HDU 4622 Reincarnation 后缀数组 或 后缀自动机
- Codeforces 427D Match & Catch 后缀自动机 或 后缀数组
- HDU 4270 Dynamic Lover 后缀自动机
- BZOJ 2806 (ctsc 2012) Cheat 后缀自动机预处理 + DP