题解 LOJ2074 「JSOI2016」无界单词(DP)
题目大意
对于一个单词\(S\),如果存在一个长度\(l\),满足\(0<l<|S|\),并且使得\(S\)长度为\(l\)的前缀与\(S\)长度为\(l\)的后缀相同,JYY 则称\(S\)是有界的。比如 aabaa
和 ababab
就都是有界的字符串。如果一个单词不存在这样的\(l\),则 JYY 称之为无界单词。
现在考虑所有仅由字母 a
和 b
组成的长度为\(n\)的字符串,JYY想知道:
- 一共有多少个无界单词?
- 这些无界单词中,按字典序排列第\(k\)小的单词是哪一个?
数据范围:\(1\leq n\leq 64\)。保证存在第\(k\)小的无界单词。
本题题解
发现“无界单词”,相当于是没有border的单词。
首先有个性质:一个长度为\(n\)的串,它的最小border长度一定小于等于\(\lfloor\frac{n}{2}\rfloor\)。因为你考虑一个长度大于\(\lfloor\frac{n}{2}\rfloor\)的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)\)解决。
对第二问,可以逐位确定。每次先将当前位字符设为\(\text{a}\)。求出在此情况下,后面的位置共有多少种填法,使得整个串是无界单词。设方案数为\(x\)。如果\(x\geq k\),那当前位就填\(\text{a}\),否则当前位填\(\text{b}\),并令\(k\)减去\(x\)。这有点像主席树上求第\(k\)大时候,先看看左边有多少个数,数量\(\geq k\)就递归左边,否则令\(k\)减去这个数量,然后递归右边。
假设当前考虑到第\(l\)位。先令第\(l\)位填\(\text{a}\)。然后我们还是效仿上面的那个DP。首先,\(dp[1]\dots dp[l]\),要么是\(0\),要么是\(1\),取决于有没有border,这都是已经确定了的,可以用一遍kmp求出(当然,也可以暴力)。然后考虑\(dp[l+1]\dots dp[n]\),\(dp[i]\) (\(l+1\leq i\leq n\))的初始值是\(2^{i-l}\)。要减去有border的情况,还是枚举最小border长度\(j\),只不过现在,转移的系数需要稍微分类讨论一下:
- 如果\(j\geq l\),那么转移时,和第一问的转移一样,只有中间的\(i-2j\)个地方可以自由确定。所以贡献是\(-dp[j]\cdot 2^{i-2j}\)。
- 如果\(j<l\),但是\(j\geq i-l\),此时这个border,前后缀两部分,都包括了一些已经确定的字符。那我们就要判断,这种转移,和已经确定的字符是否矛盾,如果矛盾,说明不存在这种情况,转移系数为\(0\)。即使不矛盾,也没有可以自由确定的位置了,所以转移系数是\(1\),贡献是\(-dp[j]\)。判断是否矛盾,其实就是看已经确定的字符里,\(s[1\dots l-j+1]\)和\(s[j\dots 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;
}