【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
来源:互联网 发布:网络公开课免费的吗 编辑:程序博客网 时间:2024/05/16 06:41
据说后缀自动机可以替代后缀数组和后缀树……
后缀自动机,用线性的节点数来保存所有的后缀。
构建自动机
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 250005using 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,求
首先对字符串s构建一个后缀自动机。之后遍历由后缀边组成的树。
用
注意:把MAXN开大一倍……
#include <iostream>#include <cstdio>#include <cstring>#define MAXN 500005using 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.对于当前节点
2.如果
3.如果
这样这道题就简单了许多,而且实现细节上木有什么坑点。
#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;}
- 【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
- 【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
- HDU4416(后缀自动机)
- hdu4416——后缀自动机
- hdu4416[多串后缀自动机]
- 【后缀自动机】[SPOJLCS]Longest Common Substring
- 【再谈后缀自动机(入门)】[SPOJLCS2]Longest Common Substring II
- 后缀自动机(不同子串的个数)hdu4416
- 后缀自动机小练总结(Spoj1811&&Hdu4416&&CodeForces235C)
- hdu4416
- 【HDU4416】Good Article Good sentence【后缀数组】
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机)
- 后缀自动机
- 后缀自动机
- JS学习第三天
- dubbo管控台安装
- iOS监听键盘弹出,获取键盘的高度
- 十大编程算法助程序员走上高手之路
- 机器学习经典算法-贝叶斯学习之朴素贝叶斯分类器
- 【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
- 第一个h5页面
- 如何理解结构体
- [eclpise+tomcat+maven spring]初体验
- 图的理解:存储结构与邻接矩阵的Java实现
- [Coursera]数据结构基础_Week4_字符串_Q2
- 解压android系统映像文件system.img
- Coco2dx 使用sqlite数据库存储以及加密数据总结
- 原生js获取 id name tagName的封装