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


    题目大意

    题目链接

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

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

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

    第一问的答案就是(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;
    }
    
  • 相关阅读:
    jUnit4初探(1)
    关于冒泡排序与选择排序
    我对直接插入排序的一点理解
    Java中的Scanner类
    String数组与字符串类
    Redis知识点详解
    MySQL操作命令详解
    java中常见面试题整理
    Redis的安装部署
    zookeeper的伪集群部署步骤
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13251723.html
Copyright © 2020-2023  润新知