题目大意
一个只由a和b组成的长度为n的字符串,如果kmp后next[n]=0,则称之为无界单词。给定n和m,求长度为n的无界单词个数以及第m小的。
第一问
正难则反!
设f[i]表示长度为i的无界单词数量。
那其实只需要算出有界单词数量,再从总数里减去即可。
枚举j为最小的使字符串前j个与后j个相等。
一个其实并不太显然的性质:
j一定小于等于i/2。
为什么呢?
嘿嘿自己想想吧(是我懒得画图了
那么
f[i]=2i−∑i2j=1f[j]∗2i−2∗j
第二问
我们当然是一位一位放,然后统计数量。
那么,就要解决一个问题——已经确定前len位的长度为n的无界单词数量是多少?
用第一问的思路,f[i]的意义改为确定前len位长度为i的无界单词数量。
j与上一问的意义一致。
那么分四种情况讨论:
1、
i<=len
此时前i位已经定了,直接判是不是无界单词即可。
2、
len<=j
显然这种会算进f[j]里。
剩余随意填,是
2i−2∗j
3、
j<len<=i−j
可以随意填的是
2i−2∗j−(len−j)
4、
len>i−j
懒得上图……
我们作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");
}
}