【SPOJ7258】Lexicographical Substring Search-后缀自动机+拓补序递推

测试地址:Lexicographical Substring Search

题目大意:给定一个字符串,有Q个询问,每个询问字符串中字典序第Ki小的本质不同的子串。

做法:这几天学习了后缀自动机(Suffix Automaton,SAM),个人感觉除了clj的原论文,写得最好的就是这个,我就是看着这个学会后缀自动机的。里面最后讲的一道例题就是这道题,首先对字符串构建后缀自动机,然后对于每个点维护一个值s,指从这个点出发最多能找到的子串数,显然我们可以按原图的反向拓补序依次求出每一个点的s,所以我们可以用类似记忆化搜索的方法递推,搜索到没算过的点继续往下算,算过的就直接累加,当然从一个点出发能找到的子串也包括这个点自身,所以回来时记得+1。然后对于每个询问,从起始节点开始,按照字典序从小到大扫过每个儿子,如果K大于当前儿子能找到的子串数,则将K减去这个数,然后找下一个儿子,直到找到一个儿子使得从它出发能找到的子串数≥K,就输出这个儿子的字母,然后从这个儿子往下寻找。因为从一个点出发能找到的子串也包括这个点自身,所以每走过一个点,都要把K减去1,当K等于0的时候就结束。这样,总的时间复杂度就是O(NQ),可以通过。

我犯二的地方:第一次写想错了,把最后一个插入的点当成最后一个可以接受后缀的点......显然错了,要分清楚这两个概念。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
char s[100010];
int n,q,tot=0,in[200010]={0},last=0;
struct SAMnode
{
  int pre,ch[30],step,s;
}nd[200010];
bool vis[200010]={0};

void extend(int c)
{
  int p=last,q,np=++tot,nq;
  nd[np].step=nd[p].step+1;
  for(int i=0;i<=25;i++) nd[np].ch[i]=-1;
  while(nd[p].ch[c]==-1&&p!=-1)
  {
    nd[p].ch[c]=np;
	p=nd[p].pre;
  }
  if (p!=-1)
  {
    q=nd[p].ch[c];
	if (nd[q].step==nd[p].step+1)
	{
	  nd[np].pre=q;
	}
	else
	{
	  nq=++tot;
	  nd[nq].pre=nd[q].pre;
	  for(int i=0;i<=25;i++) nd[nq].ch[i]=nd[q].ch[i];
	  nd[nq].step=nd[p].step+1;
	  nd[nq].s=1;
	  nd[q].pre=nq,nd[np].pre=nq;
	  while(p!=-1&&nd[p].ch[c]==q)
	  {
	    nd[p].ch[c]=nq;
		p=nd[p].pre;
	  }
	}
  }
  else nd[np].pre=0;
  nd[np].s=1;
  last=np;
}

void calc_s(int v)
{
  for(int i=0;i<=25;i++)
    if (nd[v].ch[i]!=-1)
	{
	  if (!vis[nd[v].ch[i]]) calc_s(nd[v].ch[i]);
	  nd[v].s+=nd[nd[v].ch[i]].s;
	}
  vis[v]=1;
}

void build()
{
  nd[0].pre=-1;nd[0].s=0;nd[0].step=0;
  for(int i=0;i<=25;i++) nd[0].ch[i]=-1;
  for(int i=0;i<n;i++) extend(s[i]-'a');
  for(int i=0;i<=tot;i++)
    if (nd[i].step==n) {vis[i]=1;break;}
  calc_s(0);
}

void output(int a)
{
  int v=0;
  while(a)
  {
    for(int i=0;i<=25;i++)
	  if (nd[v].ch[i]!=-1)
	  {
	    if (a>nd[nd[v].ch[i]].s) a-=nd[nd[v].ch[i]].s;
		else {v=nd[v].ch[i];printf("%c",i+'a');break;}
	  }
	a--;
  }
  if (q) printf("\n");
}

int main()
{
  scanf("%s",s);
  n=strlen(s);
  build();
  
  scanf("%d",&q);
  while(q--)
  {
    int a;
	scanf("%d",&a);
	output(a);
  }
  
  return 0;
}


posted @ 2017-04-02 11:37  Maxwei_wzj  阅读(86)  评论(0编辑  收藏  举报