[TJOI2015]弦论

题目描述

为了提高智商,ZJY 开始学习弦论。这一天,她在《String theory》中看到了这样一道问题:对于一个给定的长度为 nnn 的字符串,求出它的第 kkk 小子串是什么。你能帮帮她吗?

输入格式

第一行是一个仅由小写英文字母构成的字符串 sss。

第二行为两个整数 ttt 和 kkk,ttt 为 000 则表示不同位置的相同子串算作一个,ttt 为 111 则表示不同位置的相同子串算作多个。kkk 的意义见题目描述。

输出格式

输出数据仅有一行,该行有一个字符串,为第 kkk 小的子串。若子串数目不足 kkk 个,则输出 −1-1−1。

样例 #1

样例输入 #1

aabc
0 3

样例输出 #1

aab

样例 #2

样例输入 #2

aabc
1 3

样例输出 #2

aa

样例 #3

样例输入 #3

aabc
1 11

样例输出 #3

-1

提示

数据范围

对于 10%10\%10% 的数据,n≤1000n\leq 1000n≤1000。

对于 50%50\%50% 的数据,t=0t = 0t=0。

对于 100%100\%100% 的数据,1≤n≤5×1051\leq n \leq 5 \times 10^51≤n≤5×105,0≤t≤10\leq t \leq 10≤t≤1,1≤k≤1091\leq k \leq 10^91≤k≤109。


SOLUTION

子串计数可以用后缀自动机。

后缀自动机的大致思路:

每一个节点保存 endpos\mathrm{endpos}endpos (结尾出现位置)集合相同的子串(状态),往 SAM 中添加节点时, last\mathrm{last}last (上次操作的末尾)的每一个后缀状态( endpos\mathrm{endpos}endpos 不同)若没有为添加字符的连边,则向新节点连添加字符的连边(显然 )。该点需要通过 link\mathrm{link}link (后缀连接)连向该状态中,最大的一个字符串,它的最大的后缀使得二者的 endpos\mathrm{endpos}endpos 不同,如果不存在则为 000 ,存在且为它所在的状态的最大字符串,则将 link\mathrm{link}link 连向那个状态,否则,创造一个新的节点,克隆那个状态除了 len\mathrm{len}len (最大串)的信息,再将那个连向状态的且再 last\mathrm{last}last 的后缀状态中的边重定向到克隆节点。

这样 SAM 就是一个 DAGDAGDAG ,每一条路径就是子串(不重不漏),沿着最后一个加入的字符对应的节点跳 link\mathrm{link}link , 每一个经过的节点都是终止节点,即到该节点的路径为后缀。

本题中,对于 t=0t = 0t=0 的情况( root\mathrm{root}root 为初始状态):

设 dud_udu​ 表示从状态 uuu 出发向后的路径条数,那么 drootd_{root}droot​ 就是本质不同的子串的个数

那么可以得到 dud_udu​ 的转移方程(E\mathrm{E}E 表示边集):

du=1+∑(u,v,c)∈Edvd_u = 1 + \sum\limits_{(u,v,c)\in\mathrm{E}}d_vdu​=1+(u,v,c)∈E∑​dv​

之后,我们对整个 DAGDAGDAG 再进行一遍 DFSDFSDFS ,对于每个节点,按边 'a'~'z' 的顺序枚举,就可以使得枚举从小到大,则:

记录枚举到的所有状态 vvv ,dvd_vdv​ 的和为 sumsumsum ;

若按照边 iii 枚举到的下一个状态 v′v'v′ ,sum+v′<ksum + v' < ksum+v′<k ,就 sum+=ksum += ksum+=k,此时第 kkk 小一定不从 v′v'v′ 经过。

否则,第 kkk 小一定从 v′v'v′ 经过,那么我们令 sum++sum++sum++ ,即为状态 v′v'v′ 中最小的子串,并进入 v′v'v′ 递归。

等到 sum=ksum = ksum=k 时,说明已经找到了答案,返回即可。

复杂度 O(N)O(N)O(N) ,这样就可以做到 50pts50\mathrm {pts}50pts 。

下面考虑如何将 t=1t=1t=1 的情况转化为第一种:

此时要求算上重数,我们可以通过 endpos\mathrm{endpos}endpos 集合的大小来反映子串出现次数。

而对于一个节点 uuu ,考虑 link\mathrm{link}link 指向它的所有节点 viv_ivi​,那么状态 uuu 中的子串均为 viv_ivi​ 中的子串的后缀,且根据 link\mathrm{link}link 的定义,SAM建立的过程中必然存在相应的转移,使得 viv_ivi​ 的 endpos\mathrm{endpos}endpos 集合的交必然 “几乎” 与 uuu 的相等(这段可以掠过 ),即

记 SizeuSize_uSizeu​ 表示 uuu 的 endpos\mathrm{endpos}endpos 的大小,

若 uuu 不是克隆节点,则 Sizeu=1+∑SizeviSize_u=1+\sum Size_{v_i}Sizeu​=1+∑Sizevi​​, 例如 'p''pop' 中,包含 'p' 的一个子串就是它本身,所以不会有其他状态转移这个信息,因此要加一。

若 uuu 是克隆节点,则 Sizeu=∑SizeviSize_u=\sum Size_{v_i}Sizeu​=∑Sizevi​​,例如 ab'cabdab' 中,所有 'ab' 出现的位置都可以被其他子串包含(因为克隆节点是从其他子串中剥离出来的),因此不用加一。

因此可以用拓朴排序处理出这个东西。

接着,我们知道从 rootrootroot 到任意节点的路径为子串,结束位置的 endpos\mathrm{endpos}endpos 的大小就是它出现的次数,因此从一个节点 uuu 往后的路径(或者选择方式)的数量即为里面经过的状态的 SizeSizeSize 的和,这样我们就可以转化为第一种情况,此时:

du=Sizeu+∑(u,v,c)∈Edvd_u = Size_u + \sum\limits_{(u,v,c)\in\mathrm{E}}d_vdu​=Sizeu​+(u,v,c)∈E∑​dv​

不过需要注意的是,在进行最后的 DFSDFSDFS 时,如果 sum+Sizevi≥ksum+Size_{v_i} \ge ksum+Sizevi​​≥k ( viv_ivi​ 是未访问的下一个状态),那么我们的答案路径肯定是以 viv_ivi​ 结尾的(因为算重,所以此时有多种选择),这时候就需要直接返回,否则我们需要跳过当前状态节点进入下一个状态的选择,就得让 sum+=Sizevisum += Size_{v_i}sum+=Sizevi​​ 。

这样算法的时空复杂度为 O(N)O(N)O(N) ,具体实现看代码。


AC CODE

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;const int N = 5e5 + 10;struct state
{int len, link;int Next[26];
}st[N << 1];char s[N];
int n, t, k;
int deg[N << 1], Size[N << 1], d[N << 1];
int sz, last;
bool flag;void sam_init()
{st[0].len = 0; st[0].link = -1;sz ++ ; last = 0;
}void sam_extended(int c)
{int cur = sz ++ , p = last; Size[cur] = 1;st[cur].len = st[p].len + 1;while(p != -1 && st[p].Next[c] == 0){st[p].Next[c] = cur;p = st[p].link;}if(p == -1) st[cur].link = 0, deg[0] ++ ;else{int q = st[p].Next[c];if(st[q].len == st[p].len + 1) st[cur].link = q, deg[q] ++ ;else{int clone = sz ++ ;st[clone].len = st[p].len + 1;st[clone].link = st[q].link;memcpy(st[clone].Next, st[q].Next, sizeof st[q].Next);while(p != -1 && st[p].Next[c] == q){st[p].Next[c] = clone;p = st[p].link;}st[q].link = st[cur].link = clone;deg[clone] += 2;}}last = cur;
}void topsort()
{queue<int> q;for(int i = 0; i <= sz; i ++ )if(deg[i] == 0) q.push(i);while(!q.empty()){int x = q.front(); q.pop();if(st[x].link != -1){Size[st[x].link] += Size[x];if((--deg[st[x].link]) == 0) q.push(st[x].link);}}
}int dfs(int x, int op)
{if(d[x]) return d[x];d[x] = (op ? Size[x] : 1);for(int i = 0; i < 26; i ++ ){if(!st[x].Next[i]) continue;d[x] += dfs(st[x].Next[i], op);}return d[x];
}void solve(int x, int op, int sum)
{if(sum == k) return;for(int i = 0; i < 26; i ++ ){if(!st[x].Next[i]) continue;if(sum + d[st[x].Next[i]] < k) { sum += d[st[x].Next[i]]; continue; }putchar(i + 'a'); flag = 1;if(op && sum + Size[st[x].Next[i]] > k) return;solve(st[x].Next[i], op, sum + (op ? Size[st[x].Next[i]] : 1));return; }
}signed main()
{scanf("%s%lld%lld", s + 1, &t, &k);n = strlen(s + 1); sam_init();for(int i = 1; i <= n; i ++ ) sam_extended(s[i] - 'a');if(t == 1) topsort();dfs(0, t); solve(0, t, 0);if(!flag) printf("-1");putchar('\n');return 0;
}

END.

【后缀自动机】Luogu P3975 [TJOI2015]弦论题解相关推荐

  1. luogu P3975 [TJOI2015]弦论 SAM

    luogu P3975 [TJOI2015]弦论 链接 bzoj 思路 建出sam. 子串算多个的,统计preant tree的子树大小,否则就是大小为1 然后再统计sam的节点能走到多少串. 然后就 ...

  2. [Luogu P3975] [TJOI2015]弦论

    洛谷传送门 BZOJ传送门 题目描述 为了提高智商,ZJY开始学习弦论.这一天,她在< String theory>中看到了这样一道问题:对于一个给定的长度为nnn的字符串,求出它的第k& ...

  3. Luogu P3975 [TJOI2015]弦论

    题目链接 \(Click\) \(Here\) 题目大意: 重复子串不算的第\(k\)大子串 重复子串计入的第\(k\)大子串 写法:后缀自动机. 和\(OI\) \(Wiki\)上介绍的写法不太一样 ...

  4. 洛谷 [P3975 [TJOI2015]弦论

    洛谷 P3975 [TJOI2015]弦论 题目描述 给定一个长度为 nnn 的字符串,求它的第 kkk 小字串:给定 ttt, ttt 为 000 则表示不同位置的相同子串算作一个,ttt 为 11 ...

  5. 洛谷 P3975 [TJOI2015]弦论 解题报告

    P3975 [TJOI2015]弦论 题目描述 为了提高智商,ZJY开始学习弦论.这一天,她在<String theory>中看到了这样一道问题:对于一个给定的长度为\(n\)的字符串,求 ...

  6. 【题解】P3975 [TJOI2015]弦论 后缀自动机

    给定一个长度为n(5e5)的字符串,求它字典序第k小的子串. 输入还有一个t,t=0时表示不同位置的相同子串算作一个,t=1表示不同位置的相同子串算作多个. 使用后缀自动机. 后缀自动机中的每条路径对 ...

  7. 洛谷P3975 - [TJOI2015]弦论

    Portal Description 给出一个小写字母串\(s(|s|\leq5\times10^5),t\in\{0,1\},k(k\leq10^9)\),求\(s\)的第\(k\)小子串.\(t= ...

  8. P3975 [TJOI2015]弦论 第K小子串

    题目描述 https://www.luogu.org/problem/P3975 为了提高智商,ZJY开始学习弦论.这一天,她在< String theory>中看到了这样一道问题:对于一 ...

  9. BZOJ3998:[TJOI2015]弦论——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3998 https://www.luogu.org/problemnew/show/P3975 对于 ...

最新文章

  1. 机器学习流程模板及多模型对比实战梳理
  2. openstack中RemoteError: AgentNotFoundByTypeHost解决
  3. OpenCV中Mat的属性
  4. Hibernate 学习(一)
  5. 二维码原来可以这样玩
  6. GIT commit问题 No errors and 30 warnings found. Would you like to review them?
  7. 乐视股票21日起停牌,集成播控平台变“黑屏”
  8. 无线网络的设置和使用
  9. python自动注册邮箱_Python自动登录126邮箱的方法
  10. 知识存储之Apache Jena
  11. DSP6657打印ti.sysbios.heaps.HeapMem,内存问题
  12. 情人节情侣表空间白网页源码 html 简单易改 祝福你们幸福安康、地久天长!
  13. android自定义3d饼图,Android使用j4lChartAndroid插件绘制3D饼图
  14. python自动化交易_用Python寫自動交易程式的入門平台: Quantopian
  15. loadrunner11.0 安装 破解
  16. NLP实践九:HAN原理与文本分类实践
  17. Java 使用XmlUtil解析Xml
  18. 2D激光SLAM-雷达的特征点提取
  19. 【算法图文动画详解系列】QuickSort 快速排序算法
  20. 深度学习与西储大学轴承数据集(二)

热门文章

  1. mac 安装 Adobe CC XD
  2. nmp i报错git --no-replace-objects ls-remote
  3. mac 安装brew
  4. OpenCV-Python学习(19)—— OpenCV 图像几何变换之图像缩放(cv.warpAffine、cv.resize)
  5. Unity3D灯光详解
  6. ProE/Creo插件 MCADEx Tools 4.0 ForCreo
  7. 问个问题(nimultisim14.0双开关)
  8. Verilog状态机常见三种写法
  9. C#上位机开发—— 修改窗口图标和exe文件图标
  10. python如何设置搜狗输入法中英文切换_2020秋季报告:手机输入法AI时代来临,百度输入法优势明显...