P7961 [NOIP2021] 数列 题解
显然这题是个计数 dp,而当前在选哪个数,选了几个数肯定是要作为状态的,由于要求 \(Popcnt(S)\)(popcount(S) 的简称,二进制表示中 S 所有位上 1 的个数)小于等于 \(k\),然后有进位问题,因此 dp 的状态设计就出来了:设 \(f_{i,j,p,t}\) 表示目前可选的数的范围是 \([0,i]\),已经选择了 \(j\) 个数进入 \(a\) 序列,S 中前 \(0\sim i\) 位 1 的个数为 \(t\),当前位置选完之后算上前面的进位,第 \(i\) 位会往第 \(i+1\) 进位 \(p\) 个第 \(i\) 位,注意这里为了代码方便我们认为如果第 \(i\) 位选的算上前面进位共有奇数个 1 则 \(p\) 也为奇数,就是后面转移的时候直接除 2 向下取整即可。
这题填表法不太好转移(有兴趣的读者可以试试),因此考虑采用刷表法转移,枚举第 \(i+1\) 位选 \(u\) 个,就有转移方程:
其中 \(\&\) 表示按位与,\(C_{n-j}^{u}\) 是组合数,乘上组合数的原因是我们选出来的数要放到序列里面而不是可重集。
考虑初值,注意到这里 \(f_{0,?,?,?}\) 我们也需要用起来(毕竟可以选 0),因此直接考虑算出 \(f_{0,?,?,?}\) 的值,枚举选的个数 \(j\),就有 \(f_{0,j,j,j\&1}=v_0^j\times C_n^j\)。
考虑最后答案,显然前两维分别为 \(n,m\),但问题在于可能可以进超过 \(m\) 的位,因此考虑枚举第 3,4 维 \(p,t\),然后注意到 \(Popcnt(S)=t+Popcnt(p)-(p\&1)\)(\(p\) 不断往后进位得到结果就是 Popcnt(p),然后如果是奇数还要减去 1 因为 t 里面已经算过了),因此只要满足这个计算结果 \(\le k\) 的都可以作为答案,加起来即可。
复杂度视枚举情况为 \(O(n^4m)\) 或者是 \(O(n^3km)\),但是因为 \(k\le n\) 所以 \(k\) 拉满时照样是 \(O(n^4m)\)。
几个注意点:
- 随时取模,尤其是出现乘法操作,单纯加法操作最后一起取模没问题但是一旦涉及到乘法操作必须随时取模。
- 预处理组合数和 Popcnt,不然会被卡。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P7961 [NOIP2021] 数列
Date:2022/10/17
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 30 + 5, MAXM = 100 + 5;
const LL P = 998244353;
int n, m, K, Popcnt[MAXN];
LL v[MAXM], f[MAXM][MAXN][MAXN][MAXN], Mi[MAXM][MAXN], fact[MAXN], inv[MAXN], C[MAXN][MAXN];
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
LL ksm(LL a, LL b = P - 2, LL p = P)
{
LL s = 1 % p;
for (; b; b >>= 1, a = a * a % p)
if (b & 1) s = s * a % p;
return s;
}
int main()
{
n = Read(), m = Read(), K = Read(); for (int i = 0; i <= m; ++i) v[i] = Read();
fact[0] = 1; for (int i = 1; i <= n; ++i) fact[i] = fact[i - 1] * i % P;
inv[n] = ksm(fact[n]); for (int i = n - 1; i >= 0; --i) inv[i] = (i + 1) * inv[i + 1] % P;
for (int i = 0; i <= n; ++i) for (int j = 0; j <= i; ++j) C[i][j] = fact[i] * inv[j] % P * inv[i - j] % P;
for (int i = 0; i <= m; ++i) Mi[i][0] = 1;
for (int i = 0; i <= m; ++i) for (int j = 1; j <= n; ++j) Mi[i][j] = v[i] * Mi[i][j - 1] % P;
for (int i = 0; i <= n; ++i) for (int j = 9; j >= 0; --j) if ((i >> j) & 1) ++Popcnt[i];
for (int j = 0; j <= n; ++j) f[0][j][j][j & 1] = Mi[0][j] * C[n][j] % P;
for (int i = 0; i < m; ++i)
for (int j = 0; j <= n; ++j)
for (int k = 0; k <= n; ++k)
for (int t = 0; t <= K; ++t)
{
int pz = std::min(n - j, n - (k / 2));
for (int u = 0; u <= pz; ++u)
if (t + ((u + (k / 2)) & 1) <= K) f[i + 1][j + u][u + (k / 2)][t + ((u + (k / 2)) & 1)] = (f[i + 1][j + u][u + (k / 2)][t + ((u + (k / 2)) & 1)] + Mi[i + 1][u] * f[i][j][k][t] % P * C[n - j][u] % P) % P;
}
LL ans = 0;
for (int k = 0; k <= n; ++k)
for (int t = 0; t <= K; ++t)
if (t + Popcnt[k] - (k & 1) <= K) ans = (ans + f[m][n][k][t]) % P;
printf("%lld\n", ans); return 0;
}