#KMP,容斥,dp#洛谷 5770 [JSOI2016] 无界单词

题目传送门


分析

显然如果存在长度大于一半的border那么必然存在小于一半的 border,所以容斥一下,

\(dp[i]\) 表示长度为 \(i\) 的不存在公共前后缀的字符串个数,那么 \(dp[i]=2^i-\sum_{j=1}^{\lfloor\frac{i}{2}\rfloor}dp[j]\times 2^{i-2j}\)

问题就是怎么求字典序第 \(k\) 个字符串,考虑试填字母a之后统计方案数是否足够,不够就填b,

有一些区别就是前缀是固定的所以2的幂次会相应减少,并且枚举的 \(j\) 需要判断是否存在这样的 border

这就需要KMP去判断当前填的 \(i\) 个字母有哪些 border


代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N=71;
typedef unsigned long long ull;
int n,Q,flag,fail[N],v[N],s[N];
ull k,two[N],dp[N],f[N];
ull calc(int i){
    int j=i;
    while (j) v[j]=1,j=fail[j];
    for (int j=i+1;j<=n;++j){
        f[j]=two[j-i];
        for (int k=1;k*2<=j;++k)
        if (k<=j-i||v[k-(j-i)])
            f[j]-=f[k]*two[max(0,j-k-max(i,k))];
    }
    for (int i=1;i<=n;++i) v[i]=0;
    return f[n];
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    dp[0]=two[0]=1;
    for (int i=1;i<=64;++i){
        dp[i]=two[i]=two[i-1]<<1;
        for (int j=1;j*2<=i;++j) dp[i]-=dp[j]*two[i-2*j];
    }
    for (cin>>Q;Q;--Q){
        cin>>n>>k,cout<<dp[n]<<'\n';
        if (n==1){
            cout<<(k==1)?'a':'b'<<'\n';
            continue;
        }
        if (k>dp[n]/2) flag=1,k=dp[n]-k+1;
            else flag=0;
        s[1]=0,f[1]=s[n]=1;
        for (int i=2,j=0,J;i<n;++i){
            s[i]=0,J=j;
            while (j&&s[j+1]!=s[i]) j=fail[j];
            if (s[j+1]==s[i]) ++j;
            fail[i]=j,f[i]=!j;
            ull t=calc(i);
            if (t<k){
                k-=t,s[i]=1,j=J;
                while (j&&s[j+1]!=s[i]) j=fail[j];
                if (s[j+1]==s[i]) ++j;
                fail[i]=j,f[i]=!j;
            }
        }
        for (int i=1;i<=n;++i) cout<<(char)(97+(s[i]^flag));
        cout<<'\n';
    }
    return 0;
}
posted @ 2024-02-22 00:20  lemondinosaur  阅读(3)  评论(0编辑  收藏  举报