题解 LOJ2074 「JSOI2016」无界单词(DP)

题目大意

题目链接

对于一个单词\(S\),如果存在一个长度\(l\),满足\(0<l<|S|\),并且使得\(S\)长度为\(l\)的前缀与\(S\)长度为\(l\)的后缀相同,JYY 则称\(S\)是有界的。比如 aabaaababab 就都是有界的字符串。如果一个单词不存在这样的\(l\),则 JYY 称之为无界单词。

现在考虑所有仅由字母 ab 组成的长度为\(n\)的字符串,JYY想知道:

  1. 一共有多少个无界单词?
  2. 这些无界单词中,按字典序排列第\(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[i]=2^i-\sum_{j=1}^{\lfloor\frac{i}{2}\rfloor}dp[j]\cdot2^{i-2j} \]

第一问的答案就是\(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;
}
posted @ 2020-07-05 20:48  duyiblue  阅读(281)  评论(0编辑  收藏  举报