洛谷P3975 弦论

题意:求一个串的字典序第k小的子串/本质不同第k小的子串。

解:一开始我的想法是在后缀树上找,但是不知道后缀树上的边对应的是哪些字符...

然而可以不用fail树转移,用转移边转移即可。

先建一个后缀自动机,记忆化搜索每个节点向后向后有多少个串。

然后从起点开始向后一个字符一个字符的确定。

注意每到一个新点就要判断是否结束,并把k减去在此结束的串的个数。

  1 #include <cstdio>
  2 #include <cstring>
  3 
  4 const int N = 1000010;
  5 
  6 int tr[N][26], len[N], fail[N], siz[N], bin[N], cnt[N], topo[N];
  7 int top, last;
  8 
  9 inline void init() {
 10     top = last = 1;
 11     return;
 12 }
 13 
 14 inline void insert(char c) {
 15     int f = c - 'a';
 16     int p = last, np = ++top;
 17     last = np;
 18     len[np] = len[p] + 1;
 19     cnt[np] = 1;
 20     while(p && !tr[p][f]) {
 21         tr[p][f] = np;
 22         p = fail[p];
 23     }
 24     if(!p) {
 25         fail[np] = 1;
 26     }
 27     else {
 28         int Q = tr[p][f];
 29         if(len[Q] == len[p] + 1) {
 30             fail[np] = Q;
 31         }
 32         else {
 33             int nQ = ++top;
 34             len[nQ] = len[p] + 1;
 35             fail[nQ] = fail[Q];
 36             fail[Q] = fail[np] = nQ;
 37             memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
 38             while(tr[p][f] == Q) {
 39                 tr[p][f] = nQ;
 40                 p = fail[p];
 41             }
 42         }
 43     }
 44     return;
 45 }
 46 
 47 inline void sort() {
 48     for(int i = 1; i <= top; i++) {
 49         bin[len[i]]++;
 50     }
 51     for(int i = 1; i <= top; i++) {
 52         bin[i] += bin[i - 1];
 53     }
 54     for(int i = 1; i <= top; i++) {
 55         topo[bin[len[i]]--] = i;
 56     }
 57     return;
 58 }
 59 
 60 inline void count() {
 61     for(int i = top; i >= 1; i--) {
 62         int x = topo[i];
 63         cnt[fail[x]] += cnt[x];
 64     }
 65     return;
 66 }
 67 
 68 char s[N];
 69 
 70 int DFS(int x) {
 71     if(siz[x]) {
 72         return siz[x];
 73     }
 74     siz[x] = cnt[x];
 75     for(int i = 0; i < 26; i++) {
 76         if(tr[x][i]) {
 77             siz[x] += DFS(tr[x][i]);
 78         }
 79     }
 80     return siz[x];
 81 }
 82 
 83 int main() {
 84     scanf("%s", s + 1);
 85     int n = strlen(s + 1);
 86     init();
 87     for(int i = 1; i <= n; i++) {
 88         insert(s[i]);
 89     }
 90     sort();
 91     int flag, k;
 92     scanf("%d%d", &flag, &k);
 93     if(flag) {
 94         count();
 95     }
 96     else {
 97         for(int i = 2; i <= top; i++) {
 98             cnt[i] = 1;
 99         }
100     }
101 
102     int sum = 0;
103     for(int i = 2; i <= top; i++) {
104         sum += cnt[i] * (len[i] - len[fail[i]]);
105     }
106 
107     int p = 1;
108     DFS(p);
109     if(k > sum) {
110         puts("-1");
111         return 0;
112     }
113     cnt[1] = 0;
114     while(1) {
115         //printf("k = %d   %d \n", k, k == 1);
116         if(k == 1) {
117             break;
118         }
119         else {
120             k -= cnt[p];
121         }
122         //printf("k = %d p = %d \n", k, p);
123         for(int i = 0; i < 26; i++) {
124             if(!tr[p][i]) {
125                 continue;
126             }
127             if(siz[tr[p][i]] >= k) {
128                 putchar(i + 'a');
129                 p = tr[p][i];
130                 break;
131             }
132             else {
133                 k -= siz[tr[p][i]];
134             }
135         }
136     }
137 
138     return 0;
139 }
AC代码

 又思考了一下,虽然记忆化搜索会搜到重复的节点,但是这些重复所表示的是到达它的不同方案,也就是它代表的不同子串。所以需要重复统计。

这样一个节点的一条转移边其实就是它下一个字符拼上f之后能形成的子串数。

感觉好神奇...

转载于:https://www.cnblogs.com/huyufeifei/p/10230068.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值