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。

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

posted @ 2021-12-05 09:52  huayucaiji  阅读(50)  评论(0编辑  收藏  举报