洛谷 P4484 - [BJWC2018]最长上升子序列(状压 dp+打表)

洛谷题面传送门

首先看到 LIS 我们可以想到它的 \(\infty\) 种求法(bushi),但是对于此题而言,既然题目出这样一个数据范围,硬要暴搜过去也不太现实,因此我们需想到用某种奇奇怪怪的方式进行状态压缩 DP,这样一来就可以排除掉不少常用的求 DP 的方法:譬如最常用的从左往右顺着钦定元素并设 \(f_i\) 表示以 \(i\) 结尾的 LIS 的长度的方法,因此考虑换个角度,从小到大添加元素。还是设 \(f_i\) 表示以 \(i\) 结尾的 LIS 的长度,那么考虑在一轮中,我们在 \(i\)\(i+1\) 这两个位置中间插入一个比当前所有数都大的数会对 \(f\) 产生怎样的影响,有:

  • \(f_{i+1}=1+\max\limits_{j=1}^if_j\)
  • 对于 \(j>i\),有新的 \(f_{j+1}\) 等于 \(f_j\)

注意到这里涉及前缀 \(\max\),因此设 \(mf_i=\max\limits_{j=1}^if_j\),又注意到相邻两个 \(mf_i\) 的差最多为 \(1\),因此考虑 \(mf_i\)差分序列 \(d_i=mf_i-mf_{i-1}\),根据之前的推论显然它是一个 \(01\) 序列,这样就天然地形成了状压 \(dp\) 的模型。由于我们的 \(dp\) 都建立在差分序列的基础上,因此我们再来探究下加入一个比当前所有数都大的数会对差分序列产生怎样的影响:

  • \(d_{i+1}=1\)
  • 对于所有 \(j\ge i+1\),有新的 \(d_{j+1}\) 等于原来的 \(d_j\)
  • 进行以上两个操作之后,我们找到 \(i+1\) 后面第一个 \(d_j=1\)\(j\)——如果不存在这样的 \(j\) 则跳过这一步,并令 \(d_j=0\)

这样我们就可以设 \(dp_{i,j}\) 表示当前插入了 \(1\sim i\)\(d\) 的状态为 \(j\) 的方案数,转移就枚举在哪里插入 \(i+1\),然后二进制模拟 \(d\) 的变化即可。注意到 \(j\) 只用枚举到 \(2^i\),因此 \((i,j)\) 的总枚举量是 \(2^n\) 的,再加上转移的 \(n\),总复杂度 \(2^n·n\),注意滚动数组优化空间,否则会获得 MLE 0 的好成绩。

void add(int &x,int v){((x+=v)>=MOD)&&(x-=MOD);}
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int n,dp[2][134217733];
int main(){
	scanf("%d",&n);
	dp[1][1>>1]=1;int nxt=0,cur=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<(1<<i+1);j++) dp[nxt][j>>1]=0;
		for(int j=1;j<(1<<i);j+=2) if(dp[cur][j>>1]){
//			printf("%d %d %d\n",i,j,dp[cur][j]);
			for(int k=0;k<=i;k++){
				int nmsk=0;
				if(k) nmsk=j&((1<<k)-1);
				nmsk|=(1<<k);
				int rst=((1<<i)-1)^((!k)?0:((1<<k)-1));
				nmsk|=(j&rst)<<1;
				if((j&rst)){int S=j&rst;nmsk^=(S&(-S))<<1;}
				add(dp[nxt][nmsk>>1],dp[cur][j>>1]);
			}
		} swap(cur,nxt);
	} int res=0,fac=1;
	for(int i=1;i<(1<<n);i+=2) res=(res+1ll*__builtin_popcount(i)*dp[cur][i>>1])%MOD;
	for(int i=1;i<=n;i++) fac=1ll*fac*i%MOD;
	res=1ll*res*qpow(fac,MOD-2)%MOD;
	printf("%d\n",res);
	return 0;
}

直接按照上面的做法写大约会 TLE 3~6 个点,取决于你的实现,不过注意到这题长得一脸打表的样子,于是你对 \(n\) 比较大的情况在本地跑一下上面的代码,算出答案特判下即可通过此题。

并没有看懂楼下 EI 神仙的题解

void add(int &x,int v){((x+=v)>=MOD)&&(x-=MOD);}
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int n,dp[2][134217733];
int main(){
	scanf("%d",&n);
	if(n==22) return puts("749077581"),0;
	if(n==23) return puts("301075008"),0;
	if(n==24) return puts("314644758"),0;
	if(n==25) return puts("102117126"),0;
	if(n==26) return puts("819818153"),0;
	if(n==27) return puts("273498600"),0;
	if(n==28) return puts("267588741"),0;
	dp[1][1>>1]=1;int nxt=0,cur=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<(1<<i+1);j++) dp[nxt][j>>1]=0;
		for(int j=1;j<(1<<i);j+=2) if(dp[cur][j>>1]){
//			printf("%d %d %d\n",i,j,dp[cur][j]);
			for(int k=0;k<=i;k++){
				int nmsk=0;
				if(k) nmsk=j&((1<<k)-1);
				nmsk|=(1<<k);
				int rst=((1<<i)-1)^((!k)?0:((1<<k)-1));
				nmsk|=(j&rst)<<1;
				if((j&rst)){int S=j&rst;nmsk^=(S&(-S))<<1;}
				add(dp[nxt][nmsk>>1],dp[cur][j>>1]);
			}
		} swap(cur,nxt);
	} int res=0,fac=1;
	for(int i=1;i<(1<<n);i+=2) res=(res+1ll*__builtin_popcount(i)*dp[cur][i>>1])%MOD;
	for(int i=1;i<=n;i++) fac=1ll*fac*i%MOD;
	res=1ll*res*qpow(fac,MOD-2)%MOD;
	printf("%d\n",res);
	return 0;
}
posted @ 2021-10-02 23:05  tzc_wk  阅读(125)  评论(0)    收藏  举报