【GDOI2016模拟4.23】无界单词

Description

学过kmp吗?
一个只由a和b组成的字符串S,如果next[|S|]=0,那么这个单词就是无界的,否则就是有界的。
给出n和k,求长度为n的无界单词有多少个,和其中字典序第k小的是什么。
多组询问。
Type<=50,n<=64

Solution

很考验思维的一道题。
一般人(我)看到就想到鬼畜数论,结果正解是dp(也不算吧)

首先处理第一问。

好像很难做
正难则反。
Fi 表示长度为i的无界单词的数量,那么,我们只需要用 2i 减去有界单词的数量。
一个很明显的性质,如果一个有界单词最小的前后缀相等长度为j,那么它长度为j的前缀一定是无界单词。
另一个性质,j<=i/2。
那么我们可以通过枚举j,把这一段复制到后面,剩下的任选,那么

Fi=2ij=1i2Fj2i2j

并且这样是不会算重的。

那么第二问呢?

我们一位一位枚举,先填a,如果此时剩余的无界单词数量< k,那么这一位就必须填b,并且k要减去剩余的数量。
如何计算剩余的数量?
还是使用上面的思路。
假设前Len位已经确定,那么我们可以分类讨论。

1.len>=i

这时的字符串已经是确定的,那么只需要用kmp(暴力)就好了。

2.len<=j

这时我们复制是不会有重复的,也不会有任何影响,就像第一问的方法一样做就好了。

3.len>j&&len<=i-j

差不多,但是中间本来任选的地方也有一些是确定的,只需要改变任选的方案数成 2ijlen 就好了。

4.len>i-j

这样的话中间就没有任选的了,并且我们复制的时候会和原来已经确定的部分重叠,hash判断是否可行。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 65
using namespace std;
typedef long long ll;
ll f[N],mi[N],h[N],cnt,k;
int n,ty,next[N],j,ans[N],len;
int hash(int x) {
    int y=len-x;
    return h[x]==h[len]-h[y]*mi[x];
}
ll calc(int len) {
    fo(i,1,len) f[i]=!next[i];
    fo(i,len+1,n) {
        f[i]=mi[i-len];
        fo(j,1,i/2) {
            if (j>=len) cnt=mi[i-2*j];
            if (len>j&&len<=i-j) cnt=mi[i-j-len];
            if (len>i-j) cnt=hash(len-i+j);
            f[i]-=f[j]*cnt;
        }
    }
    return f[n];
}
void push(int c) {
    ans[++len]=c;h[len]=h[len-1]*2+c;
    if (len==1) return;
    while (j&&ans[j+1]!=ans[len]) j=next[j];
    next[len]=j+=(ans[j+1]==ans[len]);
}
ll work() {
    printf("%lld\n",calc(0));j=len=0;
    memset(next,0,sizeof(next));
    fo(i,1,n) {
        int l=j;push(0);
        ll cnt=calc(len);
        if (cnt<k) len--,j=l,
        push(1),k-=cnt;
    }
    fo(i,1,n) printf("%c",ans[i]+'a');
    printf("\n");
}
int main() {
    freopen("word.in","r",stdin);
    freopen("word.out","w",stdout);
    mi[0]=1;fo(i,1,64) mi[i]=mi[i-1]*2;
    for(scanf("%d",&ty);ty;ty--) 
    scanf("%d%lld",&n,&k),work();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值