题目大意
对于一个单词(S),如果存在一个长度(l),满足(0<l<|S|),并且使得(S)长度为(l)的前缀与(S)长度为(l)的后缀相同,JYY 则称(S)是有界的。比如 aabaa
和 ababab
就都是有界的字符串。如果一个单词不存在这样的(l),则 JYY 称之为无界单词。
现在考虑所有仅由字母 a
和 b
组成的长度为(n)的字符串,JYY想知道:
- 一共有多少个无界单词?
- 这些无界单词中,按字典序排列第(k)小的单词是哪一个?
数据范围:(1leq nleq 64)。保证存在第(k)小的无界单词。
本题题解
发现“无界单词”,相当于是没有border的单词。
首先有个性质:一个长度为(n)的串,它的最小border长度一定小于等于(lfloorfrac{n}{2} floor)。因为你考虑一个长度大于(lfloorfrac{n}{2} floor)的border,它的前后部分,必定有重叠,而重叠的部分,肯定也是一个border。
对第一问,可以DP。设(dp[i])表示长度为(i)的无界单词个数。我们先令(dp[i]=2^i),然后减去有border的情况。这些情况,可以考虑枚举它的最短border。因为border的border还是border,所以最短的border一定是一个不存在border的串,也就是一个无界单词!所以可以枚举最短border的长度(j),通过(dp[j])来转移:
第一问的答案就是(dp[n])。通过朴素DP可以(O(n^2))解决。
对第二问,可以逐位确定。每次先将当前位字符设为( ext{a})。求出在此情况下,后面的位置共有多少种填法,使得整个串是无界单词。设方案数为(x)。如果(xgeq k),那当前位就填( ext{a}),否则当前位填( ext{b}),并令(k)减去(x)。这有点像主席树上求第(k)大时候,先看看左边有多少个数,数量(geq k)就递归左边,否则令(k)减去这个数量,然后递归右边。
假设当前考虑到第(l)位。先令第(l)位填( ext{a})。然后我们还是效仿上面的那个DP。首先,(dp[1]dots dp[l]),要么是(0),要么是(1),取决于有没有border,这都是已经确定了的,可以用一遍kmp求出(当然,也可以暴力)。然后考虑(dp[l+1]dots dp[n]),(dp[i]) ((l+1leq ileq n))的初始值是(2^{i-l})。要减去有border的情况,还是枚举最小border长度(j),只不过现在,转移的系数需要稍微分类讨论一下:
- 如果(jgeq l),那么转移时,和第一问的转移一样,只有中间的(i-2j)个地方可以自由确定。所以贡献是(-dp[j]cdot 2^{i-2j})。
- 如果(j<l),但是(jgeq i-l),此时这个border,前后缀两部分,都包括了一些已经确定的字符。那我们就要判断,这种转移,和已经确定的字符是否矛盾,如果矛盾,说明不存在这种情况,转移系数为(0)。即使不矛盾,也没有可以自由确定的位置了,所以转移系数是(1),贡献是(-dp[j])。判断是否矛盾,其实就是看已经确定的字符里,(s[1dots l-j+1])和(s[jdots l])是否相等。如果不相等,这个长度为(j)的border就不成立。自己画个图很好理解。
- 如果(j<l),且(j<i-l),此时有(i-l-j)个地方可以自由确定。所以贡献是(-dp[j]cdot 2^{i-l-j})。
朴素的实现,因为第二种情况下判断是(O(n))的,还要枚举(i)和(j),所以整个DP是(O(n^3))的。因为要逐位确定,所以总复杂度(O(n^4))。
参考代码:
//problem:LOJ2078
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=64;
int n,nxt[MAXN+5];
char s[MAXN+5];
ull dp[MAXN+5],K;
int main() {
ull U=0;
for(int i=0;i<64;++i)U+=(1ull<<i);
int T;cin>>T;while(T--){
cin>>n>>K;
for(int i=1;i<=n;++i){
if(i==64)dp[i]=U;
else dp[i]=(1ull<<i);
for(int j=1;j<=i/2;++j){
dp[i]-=dp[j]*(1ull<<(i-2*j));
}
if(i==64)dp[i]++;
}
cout<<dp[n]<<endl;
for(int len=1;len<=n;++len){
s[len]='a';
int j=0;
nxt[1]=0;dp[1]=1;
for(int i=2;i<=len;++i){
while(j && s[j+1]!=s[i])j=nxt[j];
if(s[j+1]==s[i])++j;
nxt[i]=j;//kmp求border
if(!nxt[i])dp[i]=1;
else dp[i]=0;
}
for(int i=len+1;i<=n;++i){
dp[i]=(1ull<<(i-len));
for(int j=1;j<=i/2;++j){
ull x=1;
if(j>=len)
x=(1ull<<(i-2*j));
else if(j>=i-len){
x=1;
for(int p=i-j+1,q=1;p<=len;++p,++q){
if(s[p]!=s[q]){x=0;break;}
}
}
else
x=(1ull<<(i-len-j));
dp[i]-=dp[j]*x;
}
}
if(dp[n]<K){
K-=dp[n];
s[len]='b';
}
}
for(int i=1;i<=n;++i)cout<<s[i];cout<<endl;
}
return 0;
}