Loading

[AGC057E] RowCol/ColRow Sort(转化条件+dp)

[AGC057E] RowCol/ColRow Sort

问题的操作是对序列排序,关注的是数之间的大小关系,这时候我们可以考虑将题目的范围缩小,思考值域在 \([0,1]\) 怎么做?

手玩后发现,在这样的条件下,第一种操作即按照每行 \(0\) 的数量将行从上到下降序排序,列同理,为按照每列 \(0\) 的数量将行从上到下降序排序。

思考如何刻画 \(A\) 矩阵满足两种操作的条件。以行为例,设 \(r_i\) 表示第 \(i\)\(0\) 的数量,那么 \(A\) 矩阵所有行的 \(r\) 构成可重集合与 \(B\) 矩阵的 \(r\) 构成的可重集合相等是必要的,同时根据 \(B\) 矩阵的性质,可以得出这也是充分的。

那么刻画就是行列的可重集合与 \(B\) 相同。根据 \(B\) 矩阵的特殊性质,满足条件的 \(A\) 矩阵一定对应两个可重集合排列后唯一一种方案(每个排列并不对应唯一一种矩阵),那么 \([0,1]\) 的答案就是两个可重排列数相乘。

回到原问题,只需要将前面的方法拓展,枚举 \(k\in[0,9)\),令 \(A_{i,j}\leftarrow[A_{i,j}\le k]\)\(B_{i,j}\leftarrow[A_{i,j}\le k]\),那么一个合法的 \(A\) 矩阵一定在所有 \(k\) 上都满足上面的条件。但是方案显然不是简单的排列数相乘了,那么问题就聚焦在排列满足的条件上。

注意到操作的特殊性,枚举每个 \(k\),我们其实可以这样刻画一个矩阵 \(A\):存在两个排列 \(p^{k}(1\cdots n)\)\(q^{k}(1\cdots m)\),使得 \(A_{i,j}=B_{p^k_i,q^k_j}\)。一个合法的 \(A\) 对应唯一一个集合 \(\{{(p^0,q^0)\cdots (p^k, q^k)\}}\)。但是对于每一个 \(k+1\) 时,\(p^{k}\)\(p^{k+1}\) 有制约,具体的说,\(B_{p^k_i,q^k_j}\le k\Rightarrow B_{p^{k+1}_i,q^{k+1}_j}\le k+1\)。这里有四个排列非常难受,但实际上我们只关心两对排列的相对关系,并且不关心其他排列,所以考虑逆置换,设 \(p^{k}_i=i\),那么条件就变成:\(B_{i,j}\le k\Rightarrow B_{p^{k}_i,q^{k}_j}\le k+1\) (这里可以当作是枚举 \(k\)\(k+1\) 时满足的条件,看你怎么理解)。这样转化后单次计算 \(k\) 情况下的方案数就独立了。

继续改写条件。设 \(r_i=\sum_{j=1}^m[B_{i,j}\le k]\)\(c_{j}=\sum_{i=1}^n[A_{i,j}\le k+1]\),那么就有 \(j\le r_i\Rightarrow p^k_i\le c_{q^k_{j}}\),即为 \(p^k_i\le \min_{j\le r_i}c_{q^k_{j}}\),再根据 \(c\) 的单调性,即 \(p^k_i\le c_{\max_{j\le r_i} q^k_{j}}\)

至此我们将原问题变为:给定两个单调不升的序列 \(r\)\(c\),求满足上述的排列 \(p\)\(q\) 的方案数。考虑用 dp 求解。设 \(f_{i,j}\) 表示 \(\max(q_1\cdots q_{i})=j\) 的方案数。可以发现 \(p\)\(q\) 并不是相互制约,所以在求解过程中可以只考虑一个排列的方案数。先考虑 \(q\),转移只需要枚举当前位 \(q_i\) 是否为最大值。

怎么考虑 \(p\),可以在 dp 过程中用一个指针 \(t\) 从右往左扫描 \(r_i\),如果 \(r_t=i\),那么考虑 \(p_t\) 这一位的填法即可。

每一次 \(k\) 算出来的方案数都要除以 \(\prod_{k=1}^m(\sum_{i=1}^n[r_{i}=k])!\cdot\prod_{k=1}^m(\sum_{i=1}^n[r_{i}=k])!\),这是可重排列重复算的部分。

时间复杂度 \(O(km^2)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
#define mk std::make_pair

using u32 = unsigned int;
using u64 = unsigned long long;
using i64 = long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f;
const int N = 1.5e3 + 10, mod = 998244353;
int n, m;
int B[N][N];
i64 inv[N], fac[N], r[15][N], c[15][N], cnt[N];
i64 f[N][N], ans = 1;
i64 qpow(i64 a, i64 b) {
	i64 ret = 1;
	while(b) {
		if(b & 1) ret = ret * a % mod;
		a = a * a % mod;
		b >>= 1;
	} 
	return ret;
}
int main () {
	std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
	
	std::cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			std::cin >> B[i][j];
			r[B[i][j]][i]++;
			c[B[i][j]][j]++;
		}
	}
	int M = std::max(n, m);
	fac[0] = 1;
	for(i64 i = 1; i <= M; i++) fac[i] = fac[i - 1] * i % mod;
	inv[M] = qpow(fac[M], mod - 2);
	for(i64 i = M - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
	 
	for(int k = 0; k < 9; k++) {
		for(int j = 1; j <= n; j++) r[k + 1][j] += r[k][j];
		for(int j = 1; j <= m; j++) c[k + 1][j] += c[k][j];
		int t = n;
		for(; t && !r[k][t]; t--);
		memset(f, 0, sizeof(f));
		f[0][0] = 1;
		for(i64 i = 1; i <= m; i++) {
			i64 s = f[i - 1][0];
			for(i64 j = 1; j <= m; j++) {
				f[i][j] = ((j - i + 1) * f[i - 1][j] + s) % mod;
				s = (s + f[i - 1][j]) % mod;
			}
			for(; t && r[k][t] == i; t--) {
				for(int j = 1; j <= m; j++) f[i][j] = f[i][j] * std::max(0LL, c[k + 1][j] - t + 1) % mod;
			} 
		}
		ans = ans * f[m][m] % mod;
		memset(cnt, 0, sizeof(cnt));
		for(int i = 1; i <= n; i++) cnt[r[k][i]]++;
		for(int i = 1; i <= m; i++) ans = ans * inv[cnt[i]] % mod;
		memset(cnt, 0, sizeof(cnt));
		for(int i = 1; i <= m; i++) cnt[c[k][i]]++;
		for(int i = 0; i <= n; i++) ans = ans * inv[cnt[i]] % mod;
	}
	
	std::cout << ans << "\n";
	
	return 0;
}
posted @ 2025-02-08 19:49  Fire_Raku  阅读(42)  评论(0)    收藏  举报