[ARC190B] L Partition 题解

我们把题意转化一下:(从 \(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;
}
posted @ 2025-01-24 15:47  wfc284  阅读(6)  评论(0编辑  收藏  举报