[ARC190B] L Partition 題解

wfc284發表於2025-01-24

我們把題意轉化一下:(從 \(n\) 級 L-shape 開始倒著填)每次操作就是刪去首或尾行、首或尾列,在第 \(n-k\) 次操作後(填 \(k+1\) 級 L-shape 後),\((a,b)\) 剛好在剩下正方形的邊緣上。
也就是說,我們剩下的一定是個 \(k \times k\) 的正方形。

有一個思路:確定剩餘的 \(k \times k\) 正方形(左上角)的位置 \((x,y)\),計算其【外部貢獻】和【內部貢獻】。
\((x, y)\) 確定,發現行列獨立,用刪行刪列的方式理解,【外部貢獻】就有 \(\binom{n-k}{x-1} \binom{n-k}{y-1}\) 種方案。

我們將 \((a, b)\) 分為在剩餘正方形的頂點處和邊上:

  • 頂點處:\(k\) 級 L-shape 有 \(3\) 種填法,【內部貢獻】為 \(3 \times 4^{k-2}\)。此時 \((x, y)\) 是可以直接算出來的,故【外部貢獻】可以 \(O(1)\) 計算。
  • 邊上(不在頂點上):\(k\) 級 L-shape 有 \(2\) 種填法,【內部貢獻】為 \(2 \times 4^{k-2}\)。我們不妨令正方形的上邊貼著 \((a, b)\),則 \(x=a,y \in [b-k+2,b-1]\),即 \(i = y-1 \in [b-k+1,b-2]\),其他情況對稱處理。故一部分【外部貢獻】為:

    \[\binom{n-k}{a-1} \sum_{i=b-k+1}^{b-2}\binom{n-k}{i} \]

    \(f_k = \sum_{i=b-k+1}^{b-2}\binom{n-k}{i}\),於是:

    \[\begin{aligned} f_k &= \sum_{i=b-k+1}^{b-2} \left [ \binom{n-k-1}{i} + \binom{n-k-1}{i-1} \right] \\ &= \sum_{i=b-(k+1)+2}^{b-2} \binom{n-(k+1)}{i} + \sum_{i=b-(k+1)}^{b-3}\binom{n-(k+1)}{i} \\ &= \left [ \sum_{i=b-(k+1)+1}^{b-2} \binom{n-(k+1)}{i} \right ] - \binom{n-k-1}{b-k} + \left [\sum_{i=b-(k+1)+1}^{b-2} \binom{n-(k+1)}{i} \right ] - \binom{n-k-1}{b-2}\\ &= f_{k+1} - \binom{n-k-1}{i} + f_{k+1} - \binom{n-k-1}{b-2} \\ &= 2f_{k+1} - \binom{n-k-1}{i} - \binom{n-k-1}{b-2} \end{aligned} \]

    就可以 \(O(n)\) 預處理出 \(f\) 陣列,\(O(1)\) 詢問了。

有一個小技巧,在對稱處理的時候,只需要把 \((a, b)\)【轉】幾下,每【轉】一下算一次貢獻,具體見程式碼。

時間複雜度 \(O(n+q)\)

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define all(v) v.begin(), v.end()
#define int long long
using namespace std;

//#define filename "xxx" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
//#define multi_cases 1

namespace Traveller {
	const int N = 1e7+2, P = 998244353;
	void vadd(int &a, int b) { a = (a + b) % P; }
	const int dir[4][2] = {{0, 0}, {0, -1}, {-1, 0}, {-1, -1}};
	
	int n, a[4], b[4], q;
	
	int frac[N], inv[N], frac_inv[N];
	int pow4[N];
	void init() {
		inv[1] = 1;
		for(int i = 2; i <= n; ++i) inv[i] = (P - P/i) * inv[P % i] % P;
		frac[0] = frac_inv[0] = pow4[0] = 1;
		for(int i = 1; i <= n; ++i)
			frac[i] = frac[i-1] * i % P, 
			frac_inv[i] = frac_inv[i-1] * inv[i] % P,
			pow4[i] = pow4[i-1] * 4 % P;
	}
	int C(int n, int m) {
		if(n < 0 || m < 0 || n < m) return 0;
		return frac[n] * frac_inv[m] % P * frac_inv[n-m] % P; 
	}
	
	int f[4][N];
	void update(int *f, int a, int b) {
		for(int y = b-n+1; y <= b-2; ++y) vadd(f[n], C(0, y));
		for(int k = n-1; k >= 3; --k)
			f[k] = (2 * f[k+1] - C(n-k-1, b-k) - C(n-k-1, b-2) + 2*P) % P;
	}
	int query(int k) {
		if(k == 1) return C(n-1, a[0]-1) * C(n-1, b[0]-1) % P;
		int res = 0;
		for(int o = 0; o < 4; ++o)
			vadd(res, C(n-k, a[o]-1) * f[o][k] % P * 2 * pow4[k-2] % P);
		for(int o = 0; o < 4; ++o) {
			int x = a[0] + dir[o][0] * (k-1), y = b[0] + dir[o][1] * (k-1);
			vadd(res, C(n-k, x-1) * C(n-k, y-1) % P * 3 * pow4[k-2] % P);
		}
		return res;
	}
	
	void main() {
		cin >> n >> a[0] >> b[0] >> q;
		
		init();
		for(int o = 1; o < 4; ++o) a[o] = b[o-1], b[o] = n - a[o-1] + 1;
		for(int o = 0; o < 4; ++o) update(f[o], a[o], b[o]);
		
		for(int i = 1, k; i <= q; ++i) {
			scanf("%lld", &k);
			printf("%lld\n", query(k));
		}
	}
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	while(_--) Traveller::main();
	return 0;
}

相關文章