您的位置:首页 > 其它

POJ 3882/LA4513/HDU4080/ZOJ3395 Stammering Aliens 题解【后缀自动机】

2015-04-14 12:58 155 查看
题意:求一个串中可重叠至少出现m次的最长子串,并且求出该串最后一次出现的起始位置。

找了一下网上并没有SAM做法的题解。。我来说一下好了

首先每个SAM上的结点需要多保存两个值:cnt和right。cnt代表该状态right集合大小,right值是right集合中最大的那个值(right集合定义见CLJ ppt),初值为val。

其实结点如果是复制的结点(即代码中nq)的话初值应该是原结点的right?。。没关系,我们总会更新到的

那么我们就有了两种做法:1、每次插入完暴力往上更新父亲结点的right集合大小。

2、插入完之后统一更新(如SPOJ 8222)

第一种做法是有问题的,复杂度得不到保证,于是妥妥TLE。

第二种相对比较麻烦。。由于val值大的可以更新val值小的cnt,那么先根据val值进行一次排序。然后因为所有子串都是某个前缀的某个后缀,那么我们把前缀的cnt设置为1(从网上讲解的图来看,就是最中间那一排结点cnt值全部设置为1,其它设置为0),按照val从大到小更新一下cnt和right即可。

怕跪掉所以对m=1进行了特判。。其实不特判也是可以的

#include<cstdio>
#include<algorithm>
#include<cstring>
const int MAXN=40000+5;
const int SIGMA_SIZE=26;
const int INF=~0U>>1;
struct State{
State* go[SIGMA_SIZE],*suf;
int val,cnt,right;
State():suf(0) {val=cnt=0;memset(go,0,sizeof go);}
}*root,*last;
State mem[MAXN<<1],*cur;
int ans1,ans2;
int m;
inline void init()
{
ans1=-1;
cur=mem;
mem[0]=State();
last=root=cur++;
}
inline void extend(int w)
{
State* p=last,*np=cur++;
*np=State();
np->right=np->val=p->val+1;
while(p && !p->go[w]) p->go[w]=np,p=p->suf;
if(!p) np->suf=root;
else
{
State* q=p->go[w];
if(q->val==p->val+1) np->suf=q;
else
{
State* nq=cur++;
*nq=State();
memcpy(nq->go,q->go,sizeof q->go);
nq->right=nq->val=p->val+1;
nq->suf=q->suf;
q->suf=np->suf=nq;
while(p && p->go[w]==q) p->go[w]=nq,p=p->suf;
}
}
last=np;
}
inline int idx(char c)
{
return c-'a';
}
char s[MAXN];
int n;
State* pt[MAXN<<1];
void work()
{
static int ws[MAXN<<1];
State* t;
for(int i=0;i<=n;++i) ws[i]=0;
for(t=mem+1;t!=cur;++t) ++ws[t->val];
for(int i=1;i<=n;++i) ws[i]+=ws[i-1];
for(t=cur-1;t!=mem;--t) pt[--ws[t->val]]=t;
t=root;
for(int i=0;i<n;++i) t=t->go[idx(s[i])],t->cnt++;
for(int i=cur-mem-2;i>=0;--i)
{
State* u=pt[i];
if(u->cnt>=m)
{
if(u->val>ans1) ans1=u->val,ans2=u->right-u->val;
else if(u->val==ans1) ans2=std::max(ans2,u->right-u->val);
}
if(u->suf)
u->suf->cnt+=u->cnt,u->suf->right=std::max(u->suf->right,u->right);
}
}
int main()
{
//freopen("1.in","r",stdin);
while(scanf("%d",&m)!=EOF && m)
{
init();
scanf("%s",s);
n=strlen(s);
if(m==1)
{
printf("%d 0\n",n);
continue;
}
for(int i=0;i<n;++i)
extend(idx(s[i]));
work();
if(ans1==-1) puts("none");
else printf("%d %d\n",ans1,ans2);
}
return 0;
}


View Code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: