「 題解」NOIP2021模擬賽(2021-07-19)

微笑是糖゛甜到憂傷づ發表於2021-07-19

小兔的話

歡迎大家在評論區留言哦~


D - 矩陣

簡單題意

一個 \(i * i\)\(01\) 矩陣,若滿足 每一行每一列 都滿足 恰好\(2\) 個位置是 \(1\) 時,稱為 \(i\) 級配對矩陣
\(i\) 級配對矩陣的個數為 \(f_i\);請求出:\(\sum_{i = 1}^n f_i\),答案對 \(998244353\) 取模

資料範圍

\(1 \leq n \leq 10^7\)

知識點

  • 動態規劃(\(dp\)

分析

題意轉換

這個題目有點複雜,換成一個能更好理解題目解析的:
有一個長度為 \(i\) 的序列,初始狀態時全部的數都為 \(0\)
\(i\) 次操作,每一次操作需要選擇 \(2\) 個不同的位置,並把其所對應的數 \(+1\)
\(f_i\) 定義為能使原序列的數全部變成 \(2\) 的操作方案數;請求出:\(\sum_{i = 1}^n f_i\),答案對 \(998244353\) 取模

  • 矩陣有 \(i\)\(\to\) 長度為 \(i\) 的序列
  • 每一行的 \(1\)\(2\) 個位置 \(\to\) 選擇 \(2\) 個位置 \(+1\)
  • 矩陣有 \(i\)\(\to\) \(i\) 次操作
  • 每一列都恰好滿足有 \(2\) 個位置是 \(1\) \(\to\) 使原序列的數全部變成 \(2\)
  • 每一列的 \(1\)\(2\) 個位置 \(\to\) 該位置對應的 \(2\)\(+1\) 操作

題目解析

  • 特殊說明:\(dp_i\) 表示原題目中的 \(f_i\)\(A\) 表示排列數;\(C\) 表示組合數
  • 特殊說明:\(F_i\) 表示序列中已經進行了 \(1\) 次操作的方案數(即有 \(2\) 個位置已經是 \(1\) 了,剩下 \(i-1\) 次操作)

對於一個長度為 \(i\) 的空序列,考慮某個位置的 \(2\) 次操作
不妨考慮位置 \(1\)(任意一個都可以)的 \(2\) 次操作,這 \(2\) 次操作對位置 \(1\) 的總貢獻是一樣的(使位置 \(1\) 的數變為 \(2\)),就可以轉換為其餘 \(i-1\) 個位置中 \(2\) 個位置(可以相同)的 \(+1\) 操作,接下來討論操作的位置(其餘的 \(i-1\) 個)及其貢獻(\(dp\)):

  • \(2\) 次操作影響相同位置:\(dp_{i-2} \times (i-1) \times C_i^2\)
    • \(dp_{i-2}\):因為 \(2\) 次選擇的是相同位置,那麼就需要考慮剩下的 \(i-2\) 個位置的貢獻
    • \(i-1\):位置的可能性,有 \(i-1\) 個位置可選擇操作
    • \(C_i^2\):因為操作的順序是會影響結果的,所以需要計算 \(2\) 次操作的可能性;有 \(i\) 個操作位置,選擇其中的 \(2\) 次,又因為這 \(2\) 次操作是等價的所以是 \(C_i^2\)
  • \(2\) 次操作影響不同位置:\(F_{i-1} \times C_{i-1}^2 \times A_i^2\)
    • \(F_{i-1}\)\(2\) 次操作影響不同位置,相當於 \(i-1\) 個位置中有 \(2\) 個已經 \(+1\)
    • \(C_{i-1}^2\):在 \(i-1\) 個位置中選 \(2\) 個(不計順序)
    • \(A_i^2\):在 \(i\) 次操作選擇 \(2\) 次進行不等價操作

接下來分析 \(F\)

  • \(1\) 次操作把 \(1\) 對應的 \(2\) 個位置變成 \(2\)\(dp_{i-2} \times (i-1)\)
    • \(dp_{i-2}\):除去這 \(2\) 個位置的方案數
    • \(i-1\):在 \(i-1\) 次操作中選擇 \(1\)
  • \(2\) 次操作把 \(1\) 對應的 \(2\) 個位置變成 \(2\),同時把另外 \(1\) 個位置變為 \(2\)\(dp_{i-3} \times (i-2) \times A_{i-1}^2\)
    • \(dp_{i-3}\):除去這 \(3\) 個位置的方案數
    • \(i-2\):另外 \(1\) 個位置可能有 \(i-2\) 中可能
    • \(A_{i-1}^2\):在 \(i-1\) 次操作中選擇 \(2\) 次進行不等價操作
  • \(2\) 次操作把 \(1\) 對應的 \(2\) 個位置變成 \(2\),同時把另外 \(2\) 個位置變為 \(1\)\(F_{i-2} \times A_{i-2}^2 \times A_{i-1}^2\)
    • \(F_{i-2}\):剩下 \(i-2\) 箇中有 \(2\) 個已經位 \(1\) 的方案數
    • \(A_{i-2}^2\):在 \(i-2\) 個位置中選擇 \(2\) 個變成 \(1\),與現在的 \(2\) 個位置匹配是不等價的,所以是 \(A\)
    • \(A_{i-1}^2\):在 \(i-1\) 次操作中選擇 \(2\) 次進行不等價操作

初始化:\(dp_0 = 1, dp_2 = 1, F_2 = 1\),其餘值為 \(0\)
迴圈列舉 \(i\),進行狀態轉移,順便求出 \(\sum_{i=1}^n dp_i\) 就可以了(這種做法似乎常數很大,不建議使用 C++(NOI)

程式碼

#include <cstdio>

#define int long long

int rint()
{
	int x = 0, fx = 1; char c = getchar();
	while (c < '0' || c > '9') { fx ^= (c == '-'); c = getchar(); }
	while ('0' <= c && c <= '9') { x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); }
	if (!fx) return -x;
	return x;
}

int qpow(int u, int v, int Mod)
{
	int res = 1;
	while (v)
	{
		if (v & 1LL) res = res * u % Mod;
		u = u * u % Mod; v >>= 1;
	}
	return res;
}

const int MOD = 998244353;
const int MAX_n = 1e7;

int ans, dp[MAX_n + 5], F[MAX_n + 5];
int FAC[MAX_n + 5], inv[MAX_n + 5];

int A(int n, int m) { return FAC[n] * inv[n - m] % MOD; }
int C(int n, int m) { return A(n, m) * inv[m] % MOD; }

signed main()
{
	freopen("matrix.in", "r", stdin);
	freopen("matrix.out", "w", stdout);
	int n = rint();
	FAC[0] = 1;
	for (int i = 1; i <= n; i++)
		FAC[i] = FAC[i - 1] * i % MOD;
	inv[n] = qpow(FAC[n], MOD - 2, MOD);
	for (int i = n; i >= 1; i--)
		inv[i - 1] = inv[i] * i % MOD;
	dp[0] = 1; dp[2] = 1; F[2] = 1;
	for (int i = 3; i <= n; i++)
	{
		dp[i] = (dp[i] + dp[i - 2] * C(i, 2) % MOD * (i - 1) % MOD) % MOD;
		dp[i] = (dp[i] + F[i - 1] * A(i, 2) % MOD * C(i - 1, 2) % MOD) % MOD;
		F[i] = (F[i] + dp[i - 2] * (i - 1) % MOD) % MOD;
		F[i] = (F[i] + F[i - 2] * A(i - 1, 2) % MOD * A(i - 2, 2) % MOD) % MOD;
		F[i] = (F[i] + dp[i - 3] * A(i - 1, 2) % MOD * (i - 2) % MOD) % MOD;
	}
	for (int i = 1; i <= n; i++) ans = (ans + dp[i]) % MOD;
	printf("%lld\n", ans);
	return 0;
}