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

[AGC057E] RowCol/ColRow Sort

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

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

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

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

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

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

继续改写条件。设 ri=j=1m[Bi,jk]cj=i=1n[Ai,jk+1],那么就有 jripikcqjk,即为 pikminjricqjk,再根据 c 的单调性,即 pikcmaxjriqjk

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

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

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

时间复杂度 O(km2)

#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 @   Fire_Raku  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示