1. 程式人生 > 實用技巧 >【2020.9.12 NOIP模擬賽 T3】普及組

【2020.9.12 NOIP模擬賽 T3】普及組

題目連結

求矩陣的所有行和列的乘積均為 \(k\) 的方案數。要求矩陣是 \(n \times n\) 的,由整陣列成(可負)。\(T \le 2 \times 10^5,n \le 5 \times 10^6\),每組資料的 \(k\) 已知,質因數不超過二次。

首先符號的貢獻顯然是獨立的,\(k=1\) 的貢獻顯然是 \(2^{(n-1)^2}\)(考慮前 \((n-1) \times (n-1)\) 隨便填,用剩下一行一列來調整)。那麼剩下我們要做的就是計算出正整數的貢獻。

考慮到 \(k = 31 \times 37^2\)\(k = 2 \times 3^2\) 等價。具體地說,答案只與 \(k\)

的一次質因數和二次質因數的個數有關。而質因數之間又是獨立的。

首先考慮一次質因數的情況。那麼我們要做的就是在 \(n \times n\) 的網格中每行每列都只填一個 1 的方案數。這是個組合數學的入門題,答案是 \(n!\)

然後考慮二次質因數的情況。那麼我們要做的就是在 \(n \times n\) 的網格中填若干 \(1\)\(2\),使得每行每列的和為 \(2\)。題解中有一個轉化為圖論問題用生成函式多項式求逆求解的方法,並不會。

考慮最後一列填的什麼。如果填的是 \(2\),那麼刪去最後一列和那一行能夠轉化成子問題;如果填的是兩個 \(1\),那麼如果這兩行的另一個 \(1\)

恰好是在同一列,那麼合併並挪到最下面能夠與最右邊列是一個 \(2\) 的情況一一對應;如果不同,將那兩行合併並放到最下面,能夠與最右邊列是兩個 \(1\) 的情況對映,注意這裡的刪除合併是二對一的關係(左上右下或左下右上)。

\(f(n)\) 表示 \(n \times n\) 矩陣最右邊列為一個 \(2\) 的方案數;\(g(n)\) 表示 \(n \times n\) 矩陣最右邊列是兩個 \(1\) 的方案數。那麼轉移為:

\[f(n) \gets n(f(n-1)+g(n-1)) \]

\[g(n) \gets {n \choose 2} (f(n-1)+2g(n-1)) \]

\[Ans(n) \gets f(n)+g(n) \]

關鍵程式碼

inline void init() {
	f[1] = 0, g[1] = 1;
	for (register int i = 2; i <= 5e6; ++i) {
		g[i] = 1ll * i * (g[i - 1] + f[i - 1]) % P;
		f[i] = (1ll * i * (i - 1) / 2) % P * ((f[i - 1] << 1) + g[i - 1]) % P;
	}
}
int main() {
	...
	init();
	int _; read(_); 
	while (_--) {
		int n; read(n);
		ll ans = quickpow(jie[n], can1) * quickpow((f[n] + g[n]) % P, can2) % P;
		ans = ans * quickpow(2, 1ll * (n - 1) * (n - 1) % (P - 1)) % P;
		printf("%lld\n", (ans % P + P) % P);
	}
    return 0;
}