【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)\) \(mod\) \(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)\) \(mod\) \(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; }