[JSOI2016]无界单词

题目大意

一个只由a和b组成的长度为n的字符串,如果kmp后next[n]=0,则称之为无界单词。给定n和m,求长度为n的无界单词个数以及第m小的。

第一问

正难则反!
设f[i]表示长度为i的无界单词数量。
那其实只需要算出有界单词数量,再从总数里减去即可。
枚举j为最小的使字符串前j个与后j个相等。
一个其实并不太显然的性质:
j一定小于等于i/2。
为什么呢?
嘿嘿自己想想吧(是我懒得画图了
那么
f[i]=2ii2j=1f[j]2i2j

第二问

我们当然是一位一位放,然后统计数量。
那么,就要解决一个问题——已经确定前len位的长度为n的无界单词数量是多少?
用第一问的思路,f[i]的意义改为确定前len位长度为i的无界单词数量。
j与上一问的意义一致。
那么分四种情况讨论:
1、 i<=len
此时前i位已经定了,直接判是不是无界单词即可。
2、 len<=j
显然这种会算进f[j]里。
剩余随意填,是 2i2j
3、 j<len<=ij
可以随意填的是 2i2j(lenj)
4、 len>ij
懒得上图……
我们作j关于i/2的对称点j’
那么i-j=j’
也就是 len>j
此时要想最后出来的字符串是最小为j的有界单词,那么前len-(i-j)位和前len位的后len-(i-j)位应该相同。
直接暴力判。
详见代码。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll maxn=80+10;
ll ans[maxn],next[maxn],f[maxn],two[maxn];
ll i,j,k,l,t,n,m,ca,len,last;
ll pd(ll x){
    ll i;
    fo(i,1,x)
        if (ans[i]!=ans[len-x+i]) return 0;
    return 1;
}
ll solve(){
    ll i,j,t;
    fo(i,1,n){
        if (i<=len){
            if (!next[i]) f[i]=1;else f[i]=0;
            continue;
        }
        f[i]=two[i-len];
        fo(j,1,i/2){
            if (len<=j) t=two[i-2*j];
            else if (j<len&&len<=i-j) t=two[i-2*j-(len-j)];
            else t=pd(len-(i-j));
            f[i]-=f[j]*t;
        }
    }
    return f[n];
}
void insert(ll x){
    ans[++len]=x;
    if (len==1) return;
    while (last&&ans[last+1]!=ans[len]) last=next[last];
    if (ans[last+1]==ans[len]) last++;
    next[len]=last;
}
int main(){
    freopen("word.in","r",stdin);freopen("word.out","w",stdout);
    two[0]=1;
    fo(i,1,64) two[i]=two[i-1]*2;
    scanf("%lld",&ca);
    while (ca--){
        scanf("%lld%lld",&n,&m);
        len=last=0;
        printf("%lld\n",solve());
        fo(i,1,n){
            k=last;
            insert(0);
            t=solve();
            if (t<m){
                m-=t;
                last=k;
                len--;
                insert(1);
            }
        }
        fo(i,1,n) printf("%c",ans[i]+'a');
        printf("\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值