【Luogu】 P5770 [JSOI2016]无界单词

题目链接

添加链接描述

题目解法

我们发现无界单词即为 b o r d e r border border 长度为 0 0 0 的字符串,用 g [ i ] g[i] g[i] 表示长度为 i i i 的无界单词的个数
考虑求解 g [ i ] g[i] g[i],正难则反,用 2 i − b o r d e r 2^i-border 2iborder长度不为 0 0 0 的字符串个数
这里给出一个结论:任何长度为 l e n len len 的字符串的最小 b o r d e r border border 长度一定 ≤ ⌊ l e n 2 ⌋ \leq \lfloor\frac{len}{2}\rfloor 2len,这个结论画个图,用反证法可以简单证明出来
我们可以枚举字符串的最小 b o r d e r border border 长度 j j j,可以发现只有当字符串是无界单词时,才可能是最小的 b o r d e r border border,所以 g [ i ] = 2 i − ∑ j = 1 ⌊ i 2 ⌋ g [ j ] ∗ 2 i − 2 j g[i]=2^i-\sum^{\lfloor\frac{i}{2}\rfloor}_{j=1}g[j]*2^{i-2j} g[i]=2ij=12ig[j]2i2j

考虑第二问
一位一位往后枚举,每一位试填 0 0 0,计算无界单词的个数,决定 0 , 1 0,1 0,1
继续考虑用 d p [ i ] dp[i] dp[i] 表示前 i i i 位为无界单词的字符串个数
分类讨论(若当前试填第 l e n len len 位):

  1. i < = l e n i<=len i<=len,即前 i i i 为确定,直接暴力判断即可
  2. i > = 2 ∗ l e n i>=2*len i>=2len
    这种情况的做法和之前求 g g g 数组的方法一样
    d p [ i ] = 2 i − l e n − ∑ j = 1 ⌊ i 2 ⌋ d p [ j ] ∗ 2 i − j − m a x ( l e n , j ) dp[i]=2^{i-len}-\sum^{\lfloor\frac{i}{2}\rfloor}_{j=1}dp[j]*2^{i-j-max(len,j)} dp[i]=2ilenj=12idp[j]2ijmax(len,j)
  3. l e n < i < 2 ∗ l e n lenlen<i<2len
    可以分段考虑
    1 ≤ j ≤ i − l e n 1\leq j\leq i-len 1jilen 时,减去的为 ∑ d p [ j ] ∗ 2 i − l e n − j \sum dp[j]*2^{i-len-j} dp[j]2ilenj
    i − l e n < j ≤ ⌊ i 2 ⌋ i-lenilen<j2i 时,需要判断 [ 1 , l e n − i + j ] [1,len-i+j] [1,leni+j] [ i − j + 1 , l e n ] [i-j+1,len] [ij+1,len] 是否相同,相同减去1
#include 
using namespace std;
typedef unsigned long long ull;
const int N(70);
int n,ans[N];
ull pw[N],g[N],dp[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
bool same(int l1,int r1,int l2,int r2){
	for(int i=0;i<=r1-l1;i++) if(ans[i+l1]!=ans[i+l2]) return 0;
	return 1;
}
bool check(int rb){//判断1-rb的border是否为0 
	for(int i=1;i<=rb/2;i++) if(same(1,i,rb-i+1,rb)) return 0;
	return 1;
}
void work(){
	int n;ull k;
	scanf("%d%llu",&n,&k);
	printf("%llu\n",g[n]);
	for(int len=1;len<=n;len++){
		ans[len]=0;
		for(int i=1;i<=n;i++){
			if(i<=len) dp[i]=check(i);
			else if(i>=2*len){
				dp[i]=pw[i-len];
				for(int j=1;j<=i/2;j++) dp[i]-=dp[j]*pw[i-max(j,len)-j];
			}
			else{
				dp[i]=pw[i-len];
				for(int j=1;j<=i-len;j++) dp[i]-=dp[j]*pw[i-len-j];
				for(int j=i-len+1;j<=i/2;j++) if(same(1,len-i+j,i-j+1,len)) dp[i]-=dp[j];
			}
		}
		if(dp[n]<k) k-=dp[n],ans[len]=1;
	}
	for(int i=1;i<=n;i++) putchar(ans[i]+'a');
	puts("");
}
int main(){
	pw[0]=1;
	for(int i=1;i<=64;i++) pw[i]=pw[i-1]*2;
	for(int i=1;i<=64;i++){
		g[i]=pw[i];
		for(int j=1;j<=i/2;j++) g[i]-=g[j]*pw[i-j*2];//最短border的长度 
	}
	int T=read();
	while(T--) work();
	return 0;
}
//g[i]:长度为i的无界单词的数量 

你可能感兴趣的:(Luogu,算法)