题面
给定整数 n, m, k,和一个长度为 m + 1 的正整数数组 v_0, v_1, \ldots, v_m。
对于一个长度为 n,下标从 1 开始且每个元素均不超过 m 的非负整数序列 \{a_i\},我们定义它的权值为 v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}。
当这样的序列 \{a_i\} 满足整数 S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n} 的二进制表示中 1 的个数不超过 k 时,我们认为 \{a_i\} 是一个合法序列。
计算所有合法序列 \{a_i\} 的权值和对 998244353 取模的结果。
【数据范围】
对所有测试点保证 1 \leq k \leq n \leq 30,0 \leq m \leq 100,1 \leq v_i < 998244353。
大意
有m+1种数,分别为2^i(0 \le i \le m),每种数有一个权值。每个数可多次选,一共选n个,设这些数加起来的和为S,要求popcount(S) \le K。若按选择顺序构成的序列不同,则认为是不同方案。对于一种方案,其权值为所选数的权值的乘积。求所有可行方案的权值和。
分析
假设当前在选二进制下第i位为1的数,即2^i,选了k个,则当前二进制位会向更高一位的二进制位进位\frac{k}{2}。
当前位还可能接受来自更低一位的进位,设该进位值为p,则最终S当前位为(p+k) \bmod 2,当前位会向更高位进位\lfloor\frac{p+k}{2}\rfloor。
进位只可能向更高位进位,所以进位并不会改变当前位及更低位的1的个数,即从低位向高位枚举每一位是没有后效性的。
现在可以考虑DP了。
算法
设计DP状态。考虑需要记录哪些值。
- 显然需要记录当前在处理二进制下哪一位;
- 总共只能选n个数,需要记录现在已经选了几个数;
- 要求popcount(S) \le K,需要记录当前位及更低位已经有了几个1;
- 向更高一位的进位会对后面DP造成影响,需要将进位的值记录在状态中。
因此,设DP状态f[i][j][k][p]表示当前已处理完二进制的第i位,已经选了j个数,当前位及更低位已经有了k个1,当前位会向更高一位进位p,所有可行方案的权值和。
因为选择序列不同被认为是不同方案,即交换序列中两个不同元素会产生新的方案,我们在转移时需加以考虑。
假设选择序列中已经确定了j个位置(无需考虑是哪j个位置,因为无论是哪j个位置后续的选择方案数都是相同的),那么还有n-j个位置没有确定,假设现在要选t个相同的数,那么现在有\binom{n-j}{t}个选择方案。向当前状态转移的所有方案都可以与这\binom{n-j}{t}个方案组合,体现在权值上就是之前每种方案的的权值都贡献了\binom{n-j}{t}次。对于每种方案,当前数的权值贡献是v_i^t。因此,转移时乘上\binom{n-j}{t}v_i^t的系数即可。
转移方程如下:
i计算到m就停止,因为后面已经没法再选数了,后面的位已经由第m位的进位确定了。
最后统计答案。假设第m位向更高位进位p,那么更高位就一共有popcount(p)个1。要求popcount(S) \le K,因此答案只统计popcount(p)+k \le K的状态。
时间复杂度
枚举状态O(n^3 m),转移O(n),总时间复杂度O(n^4 m)。
代码(未卡常)
#include <bits/stdc++.h>
struct FastIO{...}; // 快读快写
using namespace std;
using ll=long long;
const int N=30,M=100,mod=998244353;
inline ll qpow(ll a,ll b){ll res=1;while(b){if(b&1) res=res*a%mod;a=a*a%mod;b>>=1;}return res;}
ll fact[N+1],inv[N+1];
inline void pre(){fact[0]=1;inv[0]=1;for(int i=1;i<=N;i++) fact[i]=fact[i-1]*i%mod,inv[i]=qpow(fact[i],mod-2);}
inline ll C(int n,int m){return fact[n]*inv[n-m]%mod*inv[m]%mod;}
int n,m,K;
ll ans;
int V[M+1];
ll f[M+1][N+1][N+1][N+1];
inline void DP(){
for(int t=0;t<=n;t++){f[0][t][t&1][t/2]=qpow(V[0],t)*C(n,t)%mod;}
for(int i=0;i<m;i++){
for(int j=0;j<=n;j++){
for(int k=0;k<=K;k++){
for(int p=0;p<=n;p++){
for(int t=0;t<=n-j;t++){
f[i+1][j+t][k+((t+p)&1)][(p+t)/2]+=f[i][j][k][p]*C(n-j,t)%mod*qpow(V[i+1],t)%mod;
}
}
}
}
}
for(int i=0;i<=K;i++){
for(int j=0;j<=n/2;j++){
if(__builtin_popcount(j)+i>K) continue;
(ans+=f[m][n][i][j])%=mod;
}
}
}
int main(){
FastIO io;
pre();
io>>n>>m>>K;
for(int i=0;i<=m;i++) io>>V[i];
DP();
io<<ans;
return 0;
}