[JZOJ4467][JSOI2016?]无界单词

题目大意

一个长度为 n ,只含有 a b 两种字符的字符串。一个串为无界单词当且仅当,该串不存在长度小于 n 的相同前后缀( 0<i<n s0..i1sni..n1 ),否则为有界单词。
要求解答两个问题:
共有多少个长度为 n 的无界单词
排名第 k 的无界单词是什么(保证第 k 名存在)
一个数据点共有 T 个测试数据。

1n64,1T5

题目分析

首先我们可以求出有界单词,然后用总数减一减得到无界单词。
为了避免重复计算,我们考虑如何表达一个有界单词?可以发现一个有界单词一定为前缀和后缀为某一无界单词,中间填上一些其它字符,而且这个无界单词是唯一的,且长度不会超过原串一半。
那么现在第一问的解决就显然了。
为了适应c++的数组下标,一下递推方程采用从 0 编号的顺序。
fi 表示枚举到第 i 位(长度为 i+1 )的串的无界单词个数。
显然可得

fi=2i+1j=02(j+1)i+1fj×2i2j1

现在考虑如何求解第二个询问,考虑我们目前已经得到长度为 len 的串,那么我们要计算采用这种串作为前缀的无界单词个数。
gi 表示枚举到第 i 位(长度为 i+1 )且使用该串作为前缀的无界单词个数,分情况讨论:

  • i<len
    直接判断已知串前 i+1 位是否是无界单词即可

否则依然使用 2i+1len 减去有界单词个数,枚举 j 表示考虑前缀 [0..j) 作为无界单词的有界单词,将问题转化为求有界单词个数:

  • len×2i+1
    • j<len :该位对 fi 的贡献为 fj×2i2j1
    • jlen :该位对 fi 的贡献为 fj×2ijlen
  • len×2>i+1
    • ij<len :该位对 fi 的贡献为 fj×2ijlen
    • ijlen :此时我们选取的部分作为后缀的时候会和已知串的后面某些位置重叠,所以我们要使用哈希、 KMP 预处理或者暴力等方法判断一下子串 [0,leni+j) 与子串 [ij,len) 是否相同,相同则贡献为 1

那么我们直接递归每一位,先填上 a ,然后计算无界单词个数,如果大于等于 k 就递归下去,否则该位填 b k 减去无界单词个数。
时间复杂度为 O(Tn3) O(Tn4) ,取决于你使用什么判断最后一种情况。

代码实现

#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;

const int N=65;

LL f[N],pw[N];
char c[2]={'a','b'};
bool s[N];
int n,T;
int p[N];
LL K;

void pre()
{
    pw[0]=1;
    for (int i=1;i<N-1;i++)
        pw[i]=pw[i-1]*2;
}

void clear()
{
    for (int i=0;i<n;i++)
        f[i]=0;
}

void calcall()
{
    clear();
    f[0]=2;
    for (int i=1;i<n;i++)
    {
        for (int j=0;(j+1)*2<=i+1;j++)
            f[i]+=f[j]*pw[i-j*2-1];
        f[i]=pw[i+1]-f[i];
    }
}

void kmp(int len)
{
    p[0]=0;
        int cur=0;
        for (int i=1;i<len;i++)
        {
                while (cur&&s[cur]!=s[i])
                        cur=p[cur-1];
                if (s[cur]==s[i])
                        cur++;
                p[i]=cur;
        }
}

bool same(int st0,int en0,int st1)
{
    for (int i=0;st0+i<=en0;i++)
        if (s[i+st0]!=s[i+st1])
            return false;
    return true;
}

LL calc(int len)
{
    clear();
    kmp(len);
    for (int i=0;i<n;i++)
        if (i<len)
            f[i]=p[i]==0;
        else
        {
            for (int j=0;(j+1)*2<=i+1;j++)
                if (len*2<=i+1&&j>=len)
                    f[i]+=f[j]*pw[i-j*2-1];
                else
                    if (len<=i-j)
                        f[i]+=f[j]*pw[i-j-len];
                    else
                        if (same(i-j,len-1,0))
                            f[i]+=f[j];
            f[i]=pw[i+1-len]-f[i];
        }
    return f[n-1];
}

void find(int cur,LL rank)
{
    if (cur==n)
        return;
    s[cur]=0;
    LL sum=calc(cur+1);
    if (sum>=rank)
    {
        find(cur+1,rank);
        return;
    }
    s[cur]=1;
    find(cur+1,rank-sum);
}

int main()
{
    pre();
    freopen("word.in","r",stdin);
    freopen("word.out","w",stdout);
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d %lld",&n,&K);
        calcall();
        printf("%lld\n",f[n-1]);
        find(0,K);
        for (int i=0;i<n;i++)
            putchar(c[s[i]]);
        putchar('\n');
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

你可能感兴趣的:(dp,字符串,OI)