前言&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。
省选继续加油吧,还有逆转乾坤的机会!