您的位置:首页 > 其它

【BZOJ3998】弦论(TJOI2015)-后缀自动机

2018-04-02 18:58 447 查看
测试地址:弦论

做法:本题需要用到后缀自动机。

先说点题外话:今天是一个特殊的日子,那就是本蒟蒻在BZOJ成功AC100道题啦!可喜可贺,可喜可贺……

好了好了,话说回来,本题要求两种东西:第KK大的子串和第KK大本质不同的子串。一看这个数据范围,就知道O(nlogn)O(nlog⁡n)的后缀数组肯定非常拙计(当然如果你会O(n)O(n)构造就当我没说……),又根据后缀自动机的性质,它从起点开始的每条不同的路径都对应本质不同的子串,那么我们就对字符串建后缀自动机。接着我们分情况讨论:

求第KK大本质不同的子串。这个我们只要DFS一遍,求出从每一个点出发的子串数,然后再按照这个在后缀自动机上走即可。

求第KK大子串。这个比上面要复杂些,因为要求每种子串出现的次数。观察发现,字符串的每一个前缀都对它所有后缀做出1的贡献,而根据后缀自动机中后缀链接的定义,实际上一个前缀会对终点在它到根在后缀链接上的路径上的所有的子串做出1的贡献,那么如果我们知道哪些点是前缀节点,我们就可以一遍DFS求出每种子串出现的数目。哪些点是前缀节点呢?实际上,只要不是在建后缀自动机的时候“克隆”某个点后出现的点,都是前缀节点。那么我们求出每种子串出现次数后,按照第一种情况做即可。

上面的所有步骤时间复杂度都是O(n)O(n)的,是很优秀的复杂度。

以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,tot=0,last,pre[1000010],ch[1000010][26],len[1000010];
int T,k,first[1000010]={0},tote=0;
int q[1000010],h,t;
ll cnt[1000010],f[1000010]={0};
char s[500010];
bool vis[1000010]={0},cln[1000010]={0};
struct edge
{
int v,next;
}e[1000010];

void init()
{
scanf("%s",s);
scanf("%d%d",&T,&k);
}

void extend(char c)
{
int p,q,np,nq;
np=++tot;
len[np]=len[last]+1;
p=last;
while(p&&!ch[p][c-'a']) ch[p][c-'a']=np,p=pre[p];
if (!p) pre[np]=1;
else
{
q=ch[p][c-'a'];
if (len[p]+1==len[q]) pre[np]=q;
else
{
nq=++tot;
cln[nq]=1;
len[nq]=len[p]+1;
pre[nq]=pre[q];
for(int i=0;i<26;i++)
ch[nq][i]=ch[q][i];
while(p&&ch[p][c-'a']==q) ch[p][c-'a']=nq,p=pre[p];
pre[q]=pre[np]=nq;
}
}
last=np;
}

void insert(int a,int b)
{
e[++tote].v=b;
e[tote].next=first[a];
first[a]=tote;
}

void build()
{
n=strlen(s);
last=++tot;
pre[last]=len[last]=0;
for(int i=0;i<26;i++)
ch[last][i]=0;
for(int i=0;i<n;i++)
extend(s[i]);
for(int i=2;i<=tot;i++)
insert(pre[i],i);
}

void dfs1(int v)
{
vis[v]=1;
cnt[v]=cln[v]?0:1;
for(int i=first[v];i;i=e[i].next)
{
if (!vis[e[i].v]) dfs1(e[i].v);
cnt[v]+=cnt[e[i].v];
}
if (T==0) cnt[v]=1;
if (v==1) cnt[v]=0;
}

void dfs2(int v)
{
vis[v]=1;
f[v]=cnt[v];
for(int i=0;i<26;i++)
if (ch[v][i])
{
if (!vis[ch[v][i]]) dfs2(ch[v][i]);
f[v]+=f[ch[v][i]];
}
}

void findans(int v,ll k)
{
if (k>f[v]) {printf("-1");return;}
k-=cnt[v];
if (k<=0) return;
for(int i=0;i<26;i++)
if (ch[v][i])
{
if (f[ch[v][i]]<k) k-=f[ch[v][i]];
else
{
printf("%c",i+'a');
findans(ch[v][i],k);
break;
}
}
}

int main()
{
init();
build();
dfs1(1);
memset(vis,0,sizeof(vis));
dfs2(1);
findans(1,k);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: