SPOJ SUBLEX Lexicographical Substring Search ( 后缀自动机)

http://www.spoj.com/problems/SUBLEX/en/

题意:

给一个字符串,每次询问字典序第k大的不重复子串。

思路:

先做个拓扑dp,求出SAM上,一个状态到种态的路径数。

拓扑dp其实就是按照拓扑序的dp啦…

然后从SAM的初始态开始,每次字典序从小到大得贪心寻找。思想类似可持久化线段树求区间第k大。

  1/* ***********************************************
  2Author :111qqz
  3Created Time :2017年11月08日 星期三 18时50分18秒
  4File Name :sublex.cpp
  5************************************************ */
  6
  7#include <bits/stdc++.h>
  8#define PB push_back
  9#define fst first
 10#define sec second
 11#define lson l,m,rt<<1
 12#define rson m+1,r,rt<<1|1
 13#define ms(a,x) memset(a,x,sizeof(a))
 14typedef long long LL;
 15#define pi pair < int ,int >
 16#define MP make_pair
 17
 18using namespace std;
 19const double eps = 1E-8;
 20const int dx4[4]={1,0,0,-1};
 21const int dy4[4]={0,-1,1,0};
 22const int inf = 0x3f3f3f3f;
 23#define MAXALP 26
 24
 25struct state
 26{
 27    int len, link, nxt[MAXALP];
 28};
 29const int N =3E5+7;
 30state st[N*2];
 31int sz, last,rt;
 32char s[N];
 33int cnt[2*N],a[2*N];//for radix sort
 34int sum[2*N]; //表示状态i到终态的路径数
 35void sa_init()
 36{
 37    sz = 0;
 38    last = rt = ++sz;
 39    st[1].len = 0;
 40    st[1].link=-1;
 41    ms(st[1].nxt,-1);
 42    ms(cnt,0);
 43}
 44
 45void sa_extend(int c)
 46{
 47    int cur = ++sz;
 48    st[cur].len = st[last].len + 1;
 49    memset(st[cur].nxt, -1, sizeof(st[cur].nxt));
 50    int p;
 51    for (p = last; p != -1 && st[p].nxt[c] == -1; p = st[p].link)
 52        st[p].nxt[c] = cur;
 53    if (p == -1) {
 54        st[cur].link = rt;
 55    } else {
 56        int q = st[p].nxt[c];
 57        if (st[p].len + 1 == st[q].len) {
 58            st[cur].link = q;
 59        } else {
 60            int clone = ++sz ;
 61            st[clone].len = st[p].len + 1;
 62            st[clone].link = st[q].link;
 63            memcpy(st[clone].nxt, st[q].nxt, sizeof(st[q].nxt));
 64            for (; p != -1 && st[p].nxt[c] == q; p = st[p].link)
 65                st[p].nxt[c] = clone;
 66            st[q].link = st[cur].link = clone;
 67        }
 68    }
 69    last = cur;
 70}
 71void query( int k) //思想类似可持久化线段树找第k大
 72//因为我们注意到,对于某个状态的所有转移,转移是j后得到的状态的所有子串的字典序一定比转移是(j+1)后得到的状态的所有子串的字典序小。
 73{
 74    int now = rt;
 75    while (k)
 76    {
 77    for ( int i = 0 ; i < 26 ; i++)//字典序从小到大寻找,找到的一定是字典序最小的。
 78        if (st[now].nxt[i]!=-1)
 79        {
 80        if (sum[st[now].nxt[i]]>=k)
 81        {
 82            putchar(('a'+i));
 83            k--;
 84            now = st[now].nxt[i];
 85            break;
 86        }
 87        else k-=sum[st[now].nxt[i]]; //类似权值线段树找第k大
 88        }
 89    }
 90    putchar('\n');
 91}
 92
 93int main()
 94{
 95    #ifndef  ONLINE_JUDGE 
 96    freopen("./in.txt","r",stdin);
 97  #endif
 98    sa_init();
 99    ms(sum,0);
100    scanf("%s",s+1);
101    int len = strlen(s+1);
102    for ( int i = 1 ; i <= len ; i++)
103    {
104        sa_extend(s[i]-'a');
105    }
106    //  a simple radix sort
107    for (int i = 1 ; i <= sz ; i++) cnt[st[i].len]++;
108    for ( int i = 1 ; i <= len ; i++) cnt[i]+=cnt[i-1];
109    for (int i = 1 ; i <= sz  ;i++) a[cnt[st[i].len]--] = i;
110//  for ( int i = 1 ; i <= sz ; i++) printf("a:%d\n",a[i]);
111    //将len按照从大到小的顺序存入临时数组a,a[1]表示len最短的状态的标号
112    //
113    //
114    //从len最长的更新,就是按照拓扑序更新。
115    //正确性在于len越大的越靠近SAM上的终止态
116    for ( int i = sz ; i >=1 ; i--)
117    {
118        int num = 0 ;
119        for ( int j = 0 ; j < 26 ; j++) if (st[a[i]].nxt[j]!=-1)
120        num+=sum[st[a[i]].nxt[j]];
121
122        sum[a[i]] = num + 1; //到终态的路径数,等于其能到达的状态到终态的路径数之和sum+该状态到终态的路径数1.
123    }
124//  for ( int i = 1 ; i <= sz ; i++) printf("sum:%d\n",sum[i]);
125    int q;
126    cin>>q;
127    while (q--)
128    {
129        int x;
130        scanf("%d",&x);
131        query(x);
132    }
133
134
135  #ifndef ONLINE_JUDGE  
136  fclose(stdin);
137  #endif
138    return 0;
139}