P7961 [NOIP2021] 数列 (DP 刷表法)
(n<=30,是个多维的DP)
v数组就是用来计算权值的,一共有m+1个。将S看做一个二进制数,按照题目S的定义,相当于在S的每一位可以随便+1(满足限制情况下),一共可以加n次。
我们来建立DP的维度,首先第一个i表示对二进制数处理到i位(从低位到高位),j表示使用了几个数(一共n个数可使用),为了满足题目要求,我们还要设置一个k表示此时的二进制一共有多少个1(题目要求不超过K),可能还会有进位的情况,再加一个p表示要向下一位进位的个数。
于是就有f[i][j][k][p](表示这四维状态下的权值和),如果用填表法不好处理,那就用刷表法向后转移:
已经选了j个数,还剩下n-j个数,我们从剩下数中选择t个加入到i+1位,再处理一下进位的情况,就有f[i+1][j+t][k+(t+p)mod2][(t+p)>>1]+=f[i][j][k][p]*pv[i][t]*C[n-j][t]。pv和C在代码中均有解释。
处理完后最高位可能还有进位的情况,再简单的处理一下就行了。
1 #include<bits/stdc++.h> 2 #define mod 998244353 3 #define ll long long 4 using namespace std; 5 ll ans, f[105][35][35][16], C[35][35], v[105], pv[105][35]; 6 7 void init(int n) {//预处理组合数 8 for (int i = 0; i <= n; i++) C[i][0] = 1; 9 for (int i = 1; i <= n; i++) 10 for (int j = 1; j <= i; j++) 11 C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod; 12 } 13 14 int popcnt(int n) {//统计二进制下1的个数 15 int res = 0; 16 while (n) res += n & 1, n >>= 1; 17 return res; 18 } 19 20 int main() { 21 init(30); 22 int n, m, K; 23 scanf("%d %d %d", &n, &m, &K); 24 for (int i = 0; i <= m; i++) { 25 scanf("%lld", &v[i]); 26 pv[i][0] = 1; 27 for (int j = 1; j <= n; j++) pv[i][j] = pv[i][j - 1] * v[i] % mod;//i^j 28 } 29 f[0][0][0][0] = 1; 30 for (int i = 0; i <= m; i++) 31 for (int j = 0; j <= n; j++) 32 for (int k = 0; k <= K; k++) 33 for (int p = 0; p <= n >> 1; p++) 34 for (int t = 0; t <= n - j; t++) { 35 f[i + 1][j + t][k + (t + p & 1)][(t + p) >> 1] = (f[i + 1][j + t][k + (t + p & 1)][(t + p) >> 1] + f[i][j][k][p] * pv[i][t] % mod * C[n - j][t] % mod) % mod; 36 } 37 for (int k = 0; k <= K; k++) 38 for (int p = 0; p <= n >> 1; p++) 39 if (k + popcnt(p) <= K) 40 ans = (ans + f[m + 1][n][k][p]) % mod; 41 printf("%lld\n", ans); 42 return 0; 43 }