【JZOJ3962】【NOI2015模拟12.27】str(回文自动机)

Problem

  给出一个字符串,求这个串中本质不同的回文子串个数以及这些本质不同的回文子串中第K小的串。

Input

这里写图片描述

Output

这里写图片描述

Hint

这里写图片描述

Solution

  首先,我们发现它要求本质不同的回文子串个数,于是马上联想到回文自动机。看看数据, 105 10 5 ,非常稳。于是第一问就用回文自动机解掉。不知道回文自动机的戳这里→这里写链接内容
  那么只剩下第二问了。很容易想到快排,然后直接输出第K小。但是理论上最坏复杂度为 O(n2log2) O ( n 2 l o g 2 ) (快排复杂度×(字符串大小比较+交换字符串))。我们发现交换字符串一定会耗费大量时间,于是可以直接把字符串的编号丢进一个数组里,然后比较时就比较它们对应的字符串,交换则交换编号。
  那么时间复杂度就变为:快排复杂度×字符串大小比较。
  上式看似很大,但是实际上我们在比较字符串大小时,基本上是 O(1) O ( 1 ) 的——因为随机数据下,两个字符串的长度一般都不相等。而且即使相等,它基本上也会在比较完前面少数字符后就知道了谁大谁小。于是此法就能优美地通过。
  时间复杂度: O() O ( 能 过 )

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
const int MAXN = 100005 ;
const int N = 26 ;
char s[MAXN];
int i,x,n,d[MAXN],head,tail,a[MAXN];
ll P,ans,K;
struct Palindromic_Tree
{
    int next[MAXN][N],fail[MAXN],num[MAXN],len[MAXN],S[MAXN],last,n,p,cur,now,fat[MAXN];
    ll cnt[MAXN];
    char ch[MAXN];
    int newnode(int l)     //新建节点
    {
        int i;
        fo(i,0,N-1)next[p][i] = 0 ;
        cnt[p] = 0 ;
        num[p] = 0 ;
        len[p] = l ;
        if(p>1)a[p-1]=p;
        return p ++ ;
    }
    inline void init()   //初始化
    {
        p = 0 ;
        newnode(0) ;
        newnode(-1) ;
        last = 0 ;
        n = 0 ;
        S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
        fail[0] = 1 ;
    }
    int get_fail(int x)     //和KMP一样,失配后找一个尽量最长的
    {
        while(S[n - len[x] - 1] != S[n]) x = fail[x] ;
        return x ;
    }
    void add(int c,int pos)
    {
        c -= 'a';
        S[++ n] = c ;
        cur = get_fail(last);   //通过上一个回文串找这个回文串的匹配位置
        if(!next[cur][c])     //如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
        {
            now = newnode(len[cur] + 2),i;   //新建节点
            fail[now] = next[get_fail(fail[cur])][c] ;   //和AC自动机一样建立fail指针,以便失配后跳转
            next[cur][c] = now ;
            fat[now]=cur;
            ch[now]=c+'a';
            num[now] = num[fail[now]] + 1 ;
        } 
        last = next[cur][c] ;
        cnt[last]++;
    }
    void count()
    {
        int i;
        fd(i,p-1,2)cnt[fail[i]]+=cnt[i] ;
    }
}run;
inline bool compare(int x,int y)
{
    if(run.len[x]!=run.len[y])return run.len[x]<run.len[y];
    while(x>1)
    {
        if(run.ch[x]!=run.ch[y])return run.ch[x]<run.ch[y];
        x=run.fat[x];
        y=run.fat[y];
    }
}
int main()
{
    scanf("%d%lld",&n,&P);
    scanf("%s",&s);
    run.init();
    fo(i,0,n-1)run.add(s[i],i);
    run.count();
    ans=run.p-2;
    printf("%lld\n",ans);
    K=P%ans+1;
    sort(a+1,a+run.p-1,compare);
    x=a[K];
    n=run.len[x];
    i=0;
    while(x>1)
    {
        s[i]=s[n-i-1]=run.ch[x];
        x=run.fat[x];
        i++;
    }
    fo(i,0,n-1)putchar(s[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值