【SAM】BZOJ3998-弦论
【题目大意】
给出一个字符串,求第k大的子串。(输入1表示子串可重复,0表示不可重复)
【思路】
显然,k大子串是后缀自动机的经典题型,可以利用后缀自动机的性质来解决。对于字符串
[前铺1]"abcbc",我们可以画出它的后缀自动机,如下图:
Pre树类似于AC自动机中的fail树,即将pre方向形成一棵树。对于上图,它的pre树如下:
[前铺2]考虑字符串s的任意非空子串t。我们称终点集合right(t)为:s中所有是t出现位置终点的集合。例如:对于字符串ATCGTCGT来说,所有的CG的末尾位置的集合是{4,7},也就是说right(“CG”)={4,7}。如果两个子串t_1和t_2终点集合一致,即right(t_1)=right(t_2),那么称它们为“终点等价”。因此,所有s的非空子串可以根据终点等价性分成若干类。
例如:对于字符串abcbc来说,它的所有子串构成的集合可以按照上述等价关系进行如下划分:{{a},{b},{c,bc},{ab},{cb,bcb,abcb},{abc},{cbc,bcbc,abcbc}}。
【知识点】pre树和right集合之间的关系
①除S以外的每个节点代表一个终点等价类。
②每个节点对应的等价类的right集合大小等于以它为根的子树的叶子节点的数量。
right等价类:
right{a}={1}
right{b}={2,4}
right{c,bc}={3,5}
right{ab} ={2}
right{cb,bcb,abcb} ={4}
right{abc} ={3}
right{cbc,bcbc,abcbc}={5}
我们用s[i]表示i所在的等价类的right集合大小,等于在pre树上以它为根的子树的叶子节点的数量。当sign=1时,s[i]=∑s[j](j为i在pre树上的孩子);当sign=0时,s[i]=1。对于sign=1的情况,显然孩子节点的step值大于父亲,所以我们只需要按照step值进行拓扑排序,从后往前进行累加即可得到s[i]的值。
对于叶子节点(叶子节点一定是非后添加的节点,即原字符串中产生的,图中的水平一行),初值在extend中产生:
s[np]=;
假设q[i]为拓扑排序后的序列,则如下累加即可:
for(int i=tot;i>=;i--)
{
if(sign==) s[pre[q[i]]]+=s[q[i]];
else s[q[i]]=;
}
s[]=;//不要忘了根节点是虚点
【解题过程】
①根据上述知识点中的性质,如何进行拓扑排序呢?
for(int i=;i<=tot;i++)
v[step[i]]++;//累加每个step[i]的个数
for(int i=;i<=tot;i++)
v[i]+=v[i-];//v[i]表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标
for(int i=tot;i>=;i--)
q[v[step[i]]--]=i;//每次将当前的i放入对应step[i]最右端的位置,然后将step[i]的最右端左移
简单地说可以理解为:将当前序列按照step值从小到大排序,对于相同的step值按照原来的出现顺序(下标顺序)从后到前排序。
如以下情况(实际的后缀自动机中是不会出现下面的例子的,这里仅仅方便理解用)
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Step[i] |
1 |
2 |
3 |
2 |
4 |
2 |
3 |
求step[i]的前最后和可得到:
i |
1 |
2 |
3 |
4 |
V[i] |
1 |
4 |
6 |
7 |
所以相当于得到了这样一张表格:
step值 |
1 |
2 |
2 |
2 |
3 |
3 |
4 |
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
q[i] |
★ |
★ |
★ |
★ |
★处即对应上面v[i]的值,表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标。从后往前依次按照step值填入★处,然后对应的v[step[i]]减一,即将★左移一位。最后我们可以得到这样的结果:
step值 |
1 |
2 |
2 |
2 |
3 |
3 |
4 |
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
q[i] |
1 |
2 |
4 |
6 |
3 |
7 |
5 |
②Sum值代表从当前状态出发不同的路径条数,即将孩子们的路径条数累加起来,再加上本身的s值。即sum[i]=s[i]+∑sum[j](j=next[i][k],k=0..25)
for(int i=tot;i>=;i--)
{
sum[q[i]]+=s[q[i]];
for(int j=;j<;j++) sum[q[i]]+=sum[next[q[i]][j]];
}
③预处理结束之后,通过dfs找出第k小的路径。这有点类似与二十六分,每次先按字典序往后走,如果当前节点的s值大于当前的k,则说明到当前节点为止,退出dfs;否则k先减去当前s的大小。如果当前节点的sum值大于当前的k值,说明终止点再它的孩子中,输出当前节点对应的字母,k并继续往下深dfs;如果当前结点的sum值小于k,说明k大的子串不在这条路径上,直接将k减去sum并继续搜索下一条路径。(说起来有点绕,直接看代码)
if (k<=s[d]) return;
k-=s[d];
for (int i=;i<;i++)
{
int tmp=next[d][i];
if (tmp>)
{
if (k<=sum[tmp])
{
printf("%c",i+'a');
dfs(tmp,k);
return;
}
k-=sum[tmp];
}
}
----搞了好久啊这道题,网上的大家都说是水题,可以得:D那我这个蒟蒻就以非常狼狈的姿势“水”过去好啦。以下代(正)码(文):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=+;
char str[MAXN];
int len,sign,k; struct SAM
{
int step[MAXN*],pre[MAXN*],next[MAXN*][],q[MAXN*];
int v[MAXN*],s[MAXN*],sum[MAXN*];
int tot,last;
inline int newNode(int cnt)
{
step[++tot]=cnt;
pre[tot]=;
for (int i=;i<;i++) next[tot][i]=;
return tot;
} inline void extend(int x)
{
int p=last;
int np=newNode(step[last]+);
s[np]=;
while (p && !next[p][x]) next[p][x]=np,p=pre[p];
if (!p) pre[np]=;
else
{
int q=next[p][x];
if (step[q]==step[p]+) pre[np]=q;
else
{
int nq=newNode(step[p]+);
for (int i=;i<;i++) next[nq][i]=next[q][i];
pre[nq]=pre[q];
pre[q]=pre[np]=nq;
while (p && next[p][x]==q) next[p][x]=nq,p=pre[p];
}
}
last=np;
}
inline void clear()
{
tot=;
last=newNode();
} inline void prep()
{
for(int i=;i<=tot;i++)
v[step[i]]++;//累加每个step[i]的个数
for(int i=;i<=tot;i++)
v[i]+=v[i-];//v[i]表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标
for(int i=tot;i>=;i--)
q[v[step[i]]--]=i;//每次将当前的i放入对应step[i]最右端的位置,然后将step[i]的最右端左移
for(int i=tot;i>=;i--)
{
if(sign==) s[pre[q[i]]]+=s[q[i]];
else s[q[i]]=;
}
s[]=;//不要忘了根节点是虚点
for(int i=tot;i>=;i--)
{
sum[q[i]]+=s[q[i]];
for(int j=;j<;j++) sum[q[i]]+=sum[next[q[i]][j]];
}
} inline void dfs(int d,int k)
{
if (k<=s[d]) return;
k-=s[d];
for (int i=;i<;i++)
{
int tmp=next[d][i];
if (tmp>)
{
if (k<=sum[tmp])
{
printf("%c",i+'a');
dfs(tmp,k);
return;
}
k-=sum[tmp];
}
}
}
}suf; void init()
{
scanf("%s",str);
suf.clear();
len=strlen(str);
for (int i=;i<len;i++) suf.extend(str[i]-'a');
scanf("%d%d",&sign,&k);
} int main()
{
init();
suf.prep();
suf.dfs(,k);
return ;
}
【SAM】BZOJ3998-弦论的更多相关文章
- BZOJ3998 弦论 【SAM】k小子串
BZOJ3998 弦论 给一个字符串,问其第\(K\)小字串是什么 两种形式 1.不同起始位置的相同串只算一次 2.不同起始位置的相同串各算一次 首先建\(SAM\) 所有串的数量就是\(SAM\)中 ...
- 【BZOJ3998】弦论 [SAM]
弦论 Time Limit: 10 Sec Memory Limit: 256 MB[Submit][Status][Discuss] Description 对于一个给定长度为N的字符串,求它的第 ...
- bzoj3998: [TJOI2015]弦论(SAM+dfs)
3998: [TJOI2015]弦论 题目:传送门 题解: SAM的入门题目(很好的复习了SAM并加强Right集合的使用) 其实对于第K小的字符串直接从root开始一通DFS就好,因为son边是直接 ...
- BZOJ3998:[TJOI2015]弦论(SAM)
Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个. ...
- 弦论(tjoi2015,bzoj3998)(sam(后缀自动机))
对于一个给定长度为\(N\)的字符串,求它的第\(K\)小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串\(S\) 第二行为两个整数\(T\)和\(K\),\(T\)为0则表示不同 ...
- bzoj3998 [TJOI2015]弦论(SAM)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3998 [题意] 询问排名第k的子串是谁,0代表相同子串不同位置算作相同,1代表相同子串 ...
- 【BZOJ3998】弦论(后缀自动机)
[BZOJ3998]弦论(后缀自动机) 题面 BZOJ 题解 这题应该很简单 构建出\(SAM\)后 求出每个点往后还能构建出几个串 按照拓扑序\(dp\)一些就好了 然后就是第\(k\)大,随便搞一 ...
- 【BZOJ3998】[TJOI2015]弦论 后缀自动机
[BZOJ3998][TJOI2015]弦论 Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T ...
- [bzoj3998][TJOI2015]弦论_后缀自动机
弦论 bzoj-3998 TJOI-2015 题目大意:给定一个字符串,求其$k$小子串. 注释:$1\le length \le 5\cdot 10^5$,$1\le k\le 10^9$. 想法: ...
- 【BZOJ-3998】弦论 后缀自动机
3998: [TJOI2015]弦论 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2018 Solved: 662[Submit][Status] ...
随机推荐
- 如何创建和销毁对象(Effective Java 第二章)
最近有在看Effective Java,特此记录下自己所体会到的东西,写篇博文会更加的加深印象,如有理解有误的地方,希望不吝赐教. 这章主题主要是介绍:何时以及如何创建对象,何时以及如何避免创建对象, ...
- Select 使用不当引发的core,你应该知道的
排查一个死机问题,搞了好几天时间,最终确定原因:最终确定问题原因,在此分享一下: 第一步:常规根据core文件查看栈信息,gdb –c core xxxx 如下rip不正确,指令地址错乱,栈信息已破坏 ...
- python中range函数与列表中删除元素
一.range函数使用 range(1,5) 代表从1到4(不包含5),结果为:1,2,3,4 ,默认步长为1 range(1,5,2) 结果为:1, 3 (同样不包含5) ,步长为2 ...
- 集合框架源码学习之LinkedList
0-1. 简介 0-2. 内部结构分析 0-3. LinkedList源码分析 0-3-1. 构造方法 0-3-2. 添加add方法 0-3-3. 根据位置取数据的方法 0-3-4. 根据对象得到索引 ...
- document.onclick在ios上不触发的解决方法与touchstart点击穿透处理
document.onclick = function (e) { var e = e ? e : window.event; var tar = e.srcElement || e.target; ...
- TCP之Nagle算法与延迟ACK
(一)Nagle算法 为了减少网络中小分组的数目,减少网络拥塞的情况.Nagle算法要求在一条TCP连接上最多只能有一个未被确认的未完成小分组,在该分组ACK到达之前不能够发送其他的小分组,发送端需要 ...
- 33.Search in Rotated Sorted Array---二分变形---《剑指offer》面试题8
题目链接 题目大意:在一个旋转数组中,判断给定的target是否存在于该旋转数组中.数组中没有重复数值.例子如下: 法一:二分.确定中间元素之后,就要判断下一步是遍历左数组还是遍历右数组.如果左数组有 ...
- centos7下opencv的安装
os:centos7 opencv:opencv3.0.0 for linux reference:http://www.cnblogs.com/xixixing/p/6096057.html det ...
- 微信小程序的下拉刷新
微信小程序的下拉刷新:在page的js文件中有监听用户下拉刷新的处理函数onPullDownRefresh:function(){} //js文件中自带的处理函数,在onUnload下面,注意不要重复 ...
- POJ-1410
Intersection Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 12817 Accepted: 3343 Des ...