后缀自动机入门。

题目描述

为了提高智商,ZJY 开始学习弦论。

这一天,她在《String theory》中看到了这样一道问题:对于一个给定的长度为 \(n\) 的字符串,求出它的第 \(k\) 小子串是什么。你能帮帮她吗?

输入输出格式

输入格式:

第一行是一个仅由小写英文字母构成的字符串 \(s\)。

第二行为两个整数 \(t\) 和 \(k\),\(t\) 为 \(0\) 则表示不同位置的相同子串算作一个。\(t\) 为 \(1\) 则表示不同位置的相同子串算作多个。\(k\) 的意义见题目描述。

输出格式:

输出数据仅有一行,该行有一个字符串,为第 \(k\) 小的子串。如果子串数目不足 \(k\) 个,则输出 \(-1\)。

输入输出样例

输入样例:

aabc
0 3

输出样例:

aab

数据范围与约定

对于 \(10\%\) 的数据,\(n\le 1000\)。

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

对于 \(100\%\) 的数据,\(n\le 5 \times 10^5,\ t<2,\ k\le 10^9\)。

题解:

SAM 学习笔记...先鸽一会。

这个题是找出第 \(k\) 大子串。因为在字符串中,第一关键字是字符,而不是长度,所以可以一个字符一个字符的比对。

那么我们先构建出 SAM,如果 \(t=1\),还需要跳 parent 树来计算一个串出现了多少次。记一个状态的贡献为 \(r\)。

然后还需要对 SAM 反向拓扑排序/记忆化搜索,得出走这条边可能到达多少种子串,也就是 DAG 后面每一条路径的 \(r\) 之和,记为 \(f\)。

然后从起始节点开始检测。每次遍历 a~z,如果发现那条边的 \(f\) 小于 \(k\),那么第 \(k\) 小的不在那边,此时让 \(k\) 减去 \(f\)。如果发现 \(f\) 大于 \(k\),则进入这条边。因为我们选了这条边,而后缀自动机的每一个状态都代表了一个子串,因此要把 \(f\) 减去新到点的 \(r\)。

如果此时发现 \(k\ge 0\),说明第 \(k\) 小的子串就在刚才那一步的状态里。否则继续在 SAM 上跑。

时间复杂度 \(O(n)\)。

Code:

#include<cstdio>
#include<cstring>
struct edge
{
int n,nxt;
edge(int n,int nxt)
{
this->n=n;
this->nxt=nxt;
}
edge(){}
}e[2000100];
int head[1000100],ecnt=-1;
void add(int from,int to)
{
e[++ecnt]=edge(to,head[from]);
head[from]=ecnt;
}
char s[500100];
int ch[26][1000100],mx[1000100],par[1000100],r[1000100],pcnt;
void build()
{
int p=pcnt=1;
for(int i=1;s[i]!='\0';++i)
{
int w=s[i]-'a';
int np=++pcnt;
mx[np]=mx[p]+1;
r[np]=1;
while(p&&!ch[w][p])
{
ch[w][p]=np;
p=par[p];
}
if(!p)
par[np]=1;
else
{
int q=ch[w][p];
if(mx[q]==mx[p]+1)
par[np]=q;
else
{
int nq=++pcnt;
mx[nq]=mx[p]+1;
while(p&&ch[w][p]==q)
{
ch[w][p]=nq;
p=par[p];
}
for(int j=0;j<26;++j)
ch[j][nq]=ch[j][q];
par[nq]=par[q];
par[q]=nq;
par[np]=nq;
}
}
p=np;
}
}
void dfs(int x)
{
for(int i=head[x];~i;i=e[i].nxt)
{
dfs(e[i].n);
r[x]+=r[e[i].n];
}
}
int d[1000100];
int q[1000100],l=0,R=0;
long long f[1000100];
int main()
{
memset(head,-1,sizeof(head));
scanf("%s",s+1);
int t,k;
scanf("%d%d",&t,&k);
build();
for(int i=2;i<=pcnt;++i)
add(par[i],i);
if(t)
dfs(1);
else
for(int i=1;i<=pcnt;++i)
r[i]=1;
memset(head,-1,sizeof(head));
ecnt=-1;
for(int i=1;i<=pcnt;++i)
{
int flag=0;
for(int j=0;j<26;++j)
if(ch[j][i])
{
flag=1;
++d[i];
add(ch[j][i],i);
}
if(!flag)
q[++R]=i;
}
while(l<R)
{
int x=q[++l];
f[x]+=r[x];
for(int i=head[x];~i;i=e[i].nxt)
{
f[e[i].n]+=f[x]; --d[e[i].n];
if(!d[e[i].n])
q[++R]=e[i].n;
}
}
t=1;
while(k>0)
{
int flag=0;
for(int i=0;i<26;++i)
{
if(f[ch[i][t]]<k)
k-=f[ch[i][t]];
else
{
flag=1;
k-=r[ch[i][t]];
printf("%c",'a'+i);
t=ch[i][t];
break;
}
}
if(!flag)
break;
}
if(k>0)
puts("-1");
return 0;
}

洛谷 P3975 / loj 2102 [TJOI2015] 弦论 题解【后缀自动机】【拓扑排序】的更多相关文章

  1. BZOJ_3998_[TJOI2015]弦论_后缀自动机

    BZOJ_3998_[TJOI2015]弦论_后缀自动机 Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行 ...

  2. 洛谷 P4248 / loj 2377 [AHOI2013] 差异 题解【后缀自动机】【树形DP】

    可能是一个 SAM 常用技巧?感觉 SAM 的基础题好多啊.. 题目描述 给定一个长度为 \(n\) 的字符串 \(S\) ,令 \(T_i\) 表示它从第 \(i\) 个字符开始的后缀,求: \[ ...

  3. 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)

    题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...

  4. [bzoj3998][TJOI2015]弦论_后缀自动机

    弦论 bzoj-3998 TJOI-2015 题目大意:给定一个字符串,求其$k$小子串. 注释:$1\le length \le 5\cdot 10^5$,$1\le k\le 10^9$. 想法: ...

  5. BZOJ3998 [TJOI2015]弦论 【后缀自动机】

    题目 对于一个给定长度为N的字符串,求它的第K小子串是什么. 输入格式 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个.T=1则表示不同位置 ...

  6. 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)

    传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ...

  7. 【洛谷 P3975】 [TJOI2015]弦论(后缀自动机)

    题目链接 建出后缀自动机. T=0,每个子串算一次,否则每个子串算该子串的\(endpos\)集合大小次. 用\(f[i]\)表示结点\(i\)表示的\(endpos\)集合大小,则\(f[i]\)为 ...

  8. 2018.12.15 bzoj3998: [TJOI2015]弦论(后缀自动机)

    传送门 后缀自动机基础题. 求第kkk小的子串(有可能要求本质不同) 直接建出samsamsam,然后给每个状态赋值之后在上面贪心选最小的(过程可以类比主席树/平衡树的查询操作)即可. 代码: #in ...

  9. BZOJ3998 TJOI2015 弦论 【后缀自动机】【贪心】

    Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个. ...

随机推荐

  1. revert

    git revert是用一次新的commit来回滚之前的commit

  2. hdu 4946 Area of Mushroom (凸包,去重点,水平排序,留共线点)

    题意: 在二维平面上,给定n个人 每个人的坐标和移动速度v 若对于某个点,只有 x 能最先到达(即没有人能比x先到这个点或者同时到这个点) 则这个点称作被x占有,若有人能占有无穷大的面积 则输出1 , ...

  3. AngularJS基本使用

    简介 AngularJS是Google开源的前端JS结构化框架 Angular关注的是动态展示页面数据, 并与用户进行交互.其主体不再是DOM,而是页面中的动态数据 AngularJS特性(优点) 双 ...

  4. HUST数媒1501班第2周作业成绩公布

    说明 本次公布的成绩对应的作业为: 第2周个人作业:WordCount编码和测试 如果同学对作业成绩存在异议,在成绩公布的72小时内(截止日期4月26日0点)可以进行申诉,方式如下: 毕博平台的第二周 ...

  5. 51建设Android版一些技术整理

    不知不觉几个月就过去了,新项目已经发了两个大的版(其实已经迭代了3版),趁着项目新版刚刚上线闲下来的时间整理下用到的技术点. 整体架构 采用MVP Android官方MVP架构示例项目解析 推荐一个插 ...

  6. 编写高质量代码改善C#程序的157个建议——建议134:有条件地使用前缀

    建议134:有条件地使用前缀 在.NET的设计规范中,不建议使用前缀.但是,即便是微软自己依然广泛的使用这前缀. 最典型的前缀是m_,这种命名一方面是考虑到历史沿革中的习惯问题,另一方面也许我们确实有 ...

  7. 关于innerHTML以及html2dom

    使用innerHTML或者insertAdjacentHTML 创建元素的时候能给我们带来很大的方便,为domNode 赋予innerHTML 属性,在插入大量的HTML的时候,使用innerHTML ...

  8. 按照已有的模板输出<一>(如发票)

    按照已有的模板输出<一> 普通的发票基本上都是固定模式,所以我们一般写好固定的模板,把其中需要变动的地方,以特定符号来代替.每次打印发票的时候,只需将其中的特定符号转换成我们需要显示的数据 ...

  9. leetcode 从排序数组中删除重复项

    最近的学习是相当的无聊,并且很无趣,每天都浪费了很多时间,比如今天下午,就是搞一手成语接龙,我也是醉了- 并且我也不知道学什么了,所以决定刷题 虽然我是0算法基础,0逻辑能力的渣渣,但是尽力每天做一道 ...

  10. tomcat异常 Socket bind failed: [730048]

    tomcat从官网站点下载时须注意版本信息: zip格式为window压缩版. tar.gz为linux安装板. installer为window安装板. 解压后的各文件功能与作用: bin:用于放置 ...