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\\)。
这个就很好转移了。我们有转移方程:
如何理解这个方程呢? \\(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\\) 了。方程如下:
其中 \\(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)的主要内容,如果未能解决你的问题,请参考以下文章