【NOIP2021 T2】数列 (sequence) 题解
【NOIP2021 T2】数列
Description
Input
Output
输出到文件 sequence.out 中。
仅一行一个整数,表示所有合法序列的权值和对 998244353 取模的结果。
Sample Input
5 1 1
2 1
Sample Output
40
【样例 2】
见选手目录下的 sequence/sequence2.in 与 sequence/sequence2.ans。
Solution
观察题目,我们发现\(a[i]\)的顺序不同对答案的贡献是相同的,所以我们考虑顺序放置\(a[i]\),然后利用组合数求出全部方案,于是这题就可以进行\(DP\)
以下我们规定关于位数的全是在二进制下讨论,二进制下最低位是第0位
设\(f[i][j][k][l]\)表示已经填完了前\(i\)个数,当前进位进到的最高位是第\(j\)位(也就是说之前的\(a[i]\)最大填到\(j-1\),即整数\(S\)最高在第\(j-1\)位填了1),\(a[i]\)中填了\(k\)个\(j\),已经有\(l\)个位是1的合法序列的权值总贡献
考虑转移,我们可以用已知的\(f[i][j][k][l]\)向其他状态转移,我们假设在\(a[i]\)中填\(num\)个\(j\),那么转移方程如下:
\(f[i+num][j+1][(k+num)/2][l+(k+num)\%2]+=f[i][j][k][l]*v_{j}^{num}*C_{i+num}^{num}\)
其中\(i+num\)表示填了这\(num\)个数后一共填完了多少个\(a[i]\),\(j+1\)表示往下进了一位(就算没有进位也可以默认进了位,因为这是没有影响的),\((k+num)/2\)表示向下一位进了几个1,\((k+num)\%2\)就是判断这个位的数是1还是0,然后再乘上一个组合数
最后答案即为\(\sum {f[n][m+1][k][l]} (count(k)+l≤K)\),其中\(count(k)\)表示数字\(k\)在二进制下有多少个位为1,\(K\)是题目中输入的\(k\)(其实就是处理最高位的进位)
CODE
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 35, M = 105;
const LL MO = 998244353;
int n, m, K;
LL v[M][N], f[N][M][N][N], C[N][N], ans;
int lowbit(int x) {
return x & (-x);
}
int count(int x) {
LL res = 0;
for (; x; x -= lowbit(x)) ++res;
return res;
}//统计一个数在二进制下有多少位是1
int main() {
freopen("sequence.in", "r", stdin); freopen("sequence.out", "w", stdout);//文件输入输出
C[0][0] = 1;
for (int i = 1; i < N; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MO;
}//预处理组合数
scanf("%d%d%d", &n, &m, &K);
for (int i = 0; i <= m; ++i) {
LL vv; scanf("%lld", &vv); v[i][0] = 1;
for (int j = 1; j <= n; ++j) v[i][j] = v[i][j - 1] * vv % MO;
}//预处理v[i]的幂
f[0][0][0][0] = 1;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= m; ++j)
for (int k = 0; k <= i / 2; ++k)
for (int l = 0; l <= K; ++l)
if (f[i][j][k][l])
for (int num = 0; num <= n - i; ++num)
f[i + num][j + 1][k + num >> 1][l + (k + num & 1)] = (f[i + num][j + 1][k + num >> 1][l + (k + num & 1)] + f[i][j][k][l] * v[j][num] % MO * C[i + num][num] % MO) % MO;//转移
for (int k = 0; k <= n; ++k)
for (int l = 0; l <= K; ++l)
if (count(k) + l <= K) ans = (ans + f[n][m + 1][k][l]) % MO;//统计答案
printf("%lld\n", ans);
return 0;
}