1. 程式人生 > 其它 >【NOIP2021 T2】數列 (sequence) 題解

【NOIP2021 T2】數列 (sequence) 題解

NOIP2021 T2

【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;
}