• 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。

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

  • 相关阅读:
    输入一个正整数n (1<n<=10),生成 1个 n*n 方阵 求出对角线之和
    关闭ubuntu讨厌的内部错误提示
    ubuntu14.04通过 gvm 安装 go语言开发环境
    codeblocks 控制台输出乱码
    opensuse13.2安装 sass和compass
    执行npm publish 报错:403 Forbidden
    执行npm publish 报错:401 Unauthorized
    使用form表单提交请求如何获取后台返回的数据?
    vscode学习(三)之如何修改打开终端的默认shell
    Agreeing to the Xcode/iOS license requires admin privileges, please run “sudo xcodebuild -license” a...
  • 原文地址:https://www.cnblogs.com/huayucaiji/p/LG7961.html
Copyright © 2020-2023  润新知