您的位置:首页 > 其它

【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416

2015-12-29 22:20 495 查看
据说后缀自动机可以替代后缀数组和后缀树……

后缀自动机,用线性的节点数来保存所有的后缀。

构建自动机

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  后缀自动机