LG7961[NOIP2021] 数列(DP)

Posted huayucaiji 的博客小屋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LG7961[NOIP2021] 数列(DP)相关的知识,希望对你有一定的参考价值。

前言&emo

关于我没有预处理组合数导致 \\(100->85\\) 这件事。

一道遗恨千年的 NOIP2021 T2。

写这篇题解时还没出官方数据和成绩。如果因为这 \\(15pts\\) 而名落孙山的话,真不知道该如何面对自己将来的 OI 生涯。

解题思路

我就按照我考场上的思考过程讲吧。

考虑 DP。(计数类不是组合数就是 DP 呀)

我们定义一个四维的状态 \\(f_{i,j,k,q}\\) 表示已经填好了 \\(i\\) 个数,考虑完了 \\(v_{1...j}\\),此时 \\(S\\) 二进制下有 \\(k\\)\\(1\\),有 \\(q\\) 个已经赋值了的 \\(a_i\\) 取了 \\(j\\)\\(q\\geq 1\\)的权值总和。

我们发现这个状态不是很好转移,因为我们无法通过 \\(q\\) 转移 \\(k\\)。问题就在于,如何计算 \\(k\\) 呢?

比如说我们现在取了 \\(x\\)\\(2^y\\),那么若 \\(2^i \\operatorname{\\&} x>0\\) 则等价于在二进制下 \\(S\\)\\(y+i\\) 位上有一个 \\(1\\)贡献,所以对 \\(k\\) 的贡献为 \\(\\operatorname{popcnt}(x)\\)\\(\\operatorname{popcnt} (x)\\) 表示 \\(x\\) 在二进制下 \\(1\\) 的位数。也就是说,我们如果单考虑 \\(S\\) 二进制下 \\(1\\) 的个数,取 \\(x\\)\\(2^i\\) 等价于取 \\(\\lfloor \\frac{x}{2^{j-i}}\\rfloor\\)\\(2^j\\)。 也就是说,后几位的数对于前面可能会有影响,我们可以把状态改成这样:

四维的状态 \\(f_{i,j,k,q}\\) 表示已经填好了 \\(i\\) 个数,考虑完了 \\(v_{1...j}\\),此时 \\(S\\) 二进制下有 \\(k\\)\\(1\\),若不考虑 \\(j\\) 以后的数,等价于取了 \\(q\\)\\(j\\)且至少有一个是本身就取了 \\(j\\)

这个就很好转移了。我们有转移方程:

\\[f_{i,j,k,q}=\\sum\\limits_{x=0}^{i} (C_i^x\\cdot \\sum\\limits_{p=0}^{j-1} \\sum\\limits_{\\lfloor tmp/2^{i-p}\\rfloor+x=q}f_{i-x,p,k-\\operatorname{popcnt}(q)+\\operatorname{popcnt}(q-x)},tmp) \\]

如何理解这个方程呢? \\(x\\) 表示有多少个数本身就取了 \\(j\\)\\(p\\) 是上一个本身被取的数字,由于我们要倒退上一个状态我们的 \\(k\\) 就会改变。我们少了 \\(x\\) 个数,不会对更低位上的 \\(1\\) 产生影响,因此 \\(k\'=k-\\operatorname{popcnt}(q)+\\operatorname{popcnt}(q-x)\\)。而 \\(tmp\\) 就是 \\(p\\) 这一位等价的个数。

我们容易发现,这个代码复杂度大约为 \\(O(n^5m^2)\\)。但是跑不满。其实严重跑不过,只有 \\(50pts\\)

我们考虑优化,我们重新定义一下状态,如下:

四维的状态 \\(f_{i,j,k,q}\\) 表示已经填好了 \\(i\\) 个数,考虑完了 \\(v_{1...j}\\),此时 \\(S\\) 二进制下有 \\(k\\)\\(1\\),若不考虑 \\(j\\) 以后的数,等价于取了 \\(q\\)\\(j\\)且可以没有任何是本身就取了 \\(j\\)

这样,我们就可以不再枚举 \\(p\\) 了。方程如下:

\\[f_{i,j,k,q}=\\sum\\limits_{x=0}^{i} (C_i^x\\cdot \\sum\\limits_{\\lfloor tmp/2\\rfloor+x=q}f_{i-x,j-1,k-\\operatorname{popcnt}(q)+\\operatorname{popcnt}(q-x)},tmp) \\]

其中 \\(tmp\\) 只有两种取值。这样,我们的复杂度就变成了 \\(O(2n^4m)\\)

似乎能过?

考场上是这么想的。

实际上,不预处理 \\(\\operatorname{popcnt}\\) 和组合数会 TLE 三个点。

预处理一下就过了,遗恨千年啊。。。。

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN=33,MAXM=100+10,MOD=998244353;

int n,m,k;
long long f[MAXN][MAXM][MAXN][MAXN];
bool vis[MAXN][MAXM][MAXN][MAXN];
long long pop[MAXM],jc[MAXM],v[MAXM],c[MAXN][MAXN];

long long qpow(long long x,int y) {
	long long ret=1;
	while(y) {
		if(y&1) {
			ret=ret*x%MOD;
		}
		y>>=1;
		x=x*x%MOD;
	}
	return ret;
}
long long inv(int x) {
	return qpow(x,MOD-2);
}

long long C(int n,int m) {
	return jc[n]*inv(jc[m])%MOD*inv(jc[n-m])%MOD;
}

long long dp(int i,int j,int k,int q) {
	if(k<0||i<0||j<0||q<0) {
		return 0ll;
	}
	if(vis[i][j][k][q]) {
		return f[i][j][k][q];
	}
	if(i==0) {
		if(q==0&&k==0) {
			return 1ll;
		}
		return 0ll;
	}
	if(j==0) {
		return 0ll;
	}
	if(q>i) {
		return 0ll;
	}
	long long ans[MAXN]={},sum=0;
	for(int x=0;x<=q;x++) {
		for(int t=(q-x)*2;t<(q-x+1)*2&&t<=i-x;t++) {
			ans[x]=(ans[x]+dp(i-x,j-1,k-pop[q]+pop[q-x],t))%MOD;
		}
	}
	for(int x=0;x<=q;x++) {
		ans[x]=ans[x]*c[i][x]%MOD;
		sum=(sum+ans[x]*qpow(v[j],x)%MOD)%MOD;
	}
	vis[i][j][k][q]=1;
	return f[i][j][k][q]=sum;
}

int pop_c(int x) {
	int ret=0;
	while(x) {
		if(x&1) {
			ret++;
		}
		x>>=1;
	}
	return ret;
}

int main() {
	
	cin>>n>>m>>k;
	m++;
	for(int i=1;i<=m;i++) {
		scanf("%lld",&v[i]);
	}
	jc[0]=1;
	for(int i=1;i<=n;i++) {
		pop[i]=pop_c(i);
		jc[i]=jc[i-1]*i%MOD;
	}
	for(int i=1;i<=n;i++) {
		for(int x=0;x<=i;x++) {
			c[i][x]=C(i,x);
		}
	}
	
	long long ans=0;
	for(int t=0;t<=k;t++) {
		for(int i=0;i<=n;i++) {
			ans=(ans+dp(n,m,t,i))%MOD;
		}
	}
	
	cout<<ans<<endl;
	
}

总结

这篇题解就写完啦!花了我 1.5h,都看到这里了,不给个赞吗qwq。

省选继续加油吧,还有逆转乾坤的机会!

以上是关于LG7961[NOIP2021] 数列(DP)的主要内容,如果未能解决你的问题,请参考以下文章

2021.5.22 noip模拟1

#2019120700019-LG 斐波那契数列

NOIP2017酱油记

2017.12.09NOIP提高组模拟赛A组

LG4158 「SCOI2009」粉刷匠 线性DP

LG2145 「JSOI2007」祖码 区间DP