【bzoj3998】[TJOI2015]弦论 后缀自动机

Description

对于一个给定长度为N的字符串,求它的第K小子串是什么。

Input

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

第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。

Output

输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1

Sample Input

aabc

0 3

Sample Output

aab

HINT

N<=5*10^5

T<2

K<=10^9

Source


众人:快看DQS个傻X连个裸题都WA两遍

窝:我不做人辣(╯‵□′)╯︵┻━┻

好吧这真的是裸题……par树原图傻傻分不清楚……

求字典序k小子串。
求出每个点往后可以到达多少个子串p -> sz,然后求k小的时候拿k和当前节点p的sz比较就知道该怎么走了…
想求出来sz,需要知道到达这个点的串的数量,记为cnt,在自动机上跑逆拓扑序用cnt求sz(相当于在树上求子树大小)

然后对于T的两个情况,初始时的cnt就不一样了。若相同的串不同位置算一个,那么每个点代表的串都是1;若不同位置算好多个,那么走到当前点p的子串数量就要加上par指针指向p的串的数量。

然后再在自动机上【不是par树上!】累加sz。

想了想我还是把调试信息删干净再发博客吧……
第二发WA是把答案计数的tot开成char这种事我才不会说呢…

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

const int SZ = 1000010;

struct sam_node{
    sam_node *ch[30],*par;
    int val,cnt,sz;
}T[SZ], *root, *last;

int Tcnt = 0,TT;

sam_node* newnode(int x)
{
    sam_node *k = T + (Tcnt ++);
    k -> val = x;
    k -> cnt = k -> sz = 0;
    memset(k -> ch,0,sizeof(k -> ch));
    k -> par = 0;
    return k;
}

void insert(int x)
{
    sam_node *p = last,*np = newnode(last -> val + 1);
    while(p && !p -> ch[x])
        p -> ch[x] = np,p = p -> par;
    if(!p)
        np -> par = root;
    else
    {
        sam_node *q = p -> ch[x];
        if(q -> val == p -> val + 1)
            np -> par = q;
        else
        {
            sam_node *nq = newnode(p -> val + 1);
            memcpy(nq -> ch,q -> ch,sizeof(nq -> ch));
            nq -> par = q -> par;
            np -> par = q -> par = nq;
            while(p && p -> ch[x] == q)
                p -> ch[x] = nq,p = p -> par;
        }
    }
    last = np;
    last -> cnt ++;
}

queue<sam_node*> q;
sam_node *S[SZ];
int top = 0;
int in[SZ];

void get_cnt()
{
    for(int i = 0;i < Tcnt;i ++)
    {
        sam_node *p = T + i;
        for(int j = 1;j <= 26;j ++)
            if(p -> ch[j])
                in[p -> ch[j] - T] ++;
    }
    q.push(root);
    while(q.size())
    {
        sam_node *p = q.front(); q.pop();
        S[++ top] = p;
        for(int i = 1;i <= 26;i ++)
            if(p -> ch[i] && !--in[p -> ch[i] - T])
                q.push(p -> ch[i]);
    }
    int t = top;
    while(t)
    {
        sam_node *p = S[t --];
        if(TT && p -> par)
            p -> par -> cnt += p -> cnt;
        else
            p -> cnt = 1;
    }
    root -> cnt = 0;  
    t = top;       
    while(t)
    {
        sam_node *p = S[t --];
        p -> sz = p -> cnt;
        for(int j = 1;j <= 26;j ++)
            if(p -> ch[j])
                p -> sz += p -> ch[j] -> sz;
    }
}

char ans[SZ];
int tot = 0;

void dfs(sam_node *p,int k)
{
    if(k <= p -> cnt) return ;
    k -= p -> cnt;
    for(int i = 1;i <= 26;i ++)
        if(p -> ch[i])
        {
            sam_node *q = p -> ch[i];
            if(q -> sz >= k)
            {
                ans[++ tot] = i + 'a' - 1;
                dfs(q,k);
                return ;
            }
            k -= q -> sz;
        }
}

void init()
{
    root = newnode(0);
    last = root;
}

char s[SZ];

int main()
{
    init();
    int k;
    scanf("%s%d%d",s,&TT,&k);
    int l = strlen(s);
    for(int i = 0;i < l;i ++)
        insert(s[i] - 'a' + 1);
    get_cnt();
    if(k > root -> sz)
        puts("-1");
    else
    {
        dfs(root,k);
        printf("%s",ans + 1);
    }
    return 0;
}
/*
aaaaa
1 4
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值