Problem
Solution
性质1: 交换行对后续的交换列没有影响,反之同理。
性质2: 如果 (x,y) 行可以交换, (y,z) 行可以交换,那么 (x,y,z) 就可以互相交换并且造成 (3!) 的贡献,进一步可以推广为若干行之间交换的贡献。列同理。
根据 性质1 和 性质2,我们可以用并查集维护可以互相交换的行、列集合大小,每个可以互相交换的集合贡献就是集合元素的全排列个数。各个贡献相乘就是答案。
Code
Talk is cheap.Show me the code.
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
return x * f;
}
const int N = 57, mod = 998244353;
int n,K;
int a[N][N],fa[N<<1],sz[N<<1],alp[N];
bool vis[N<<1];
int Find(int x) {
return (x==fa[x] ? x : fa[x] = Find(fa[x]));
}
void Join(int x,int y) {
int fx = Find(x), fy = Find(y);
if(fx != fy) {
fa[fx] = fy, sz[fy] += sz[fx];
}
}
signed main()
{
n = read(), K = read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) a[i][j] = read();
for(int i=1;i<=2*n;++i) fa[i] = i, sz[i] = 1;
alp[0] = 1;
for(int i=1;i<=n;++i) alp[i] = (alp[i-1] * i) % mod;
for(int x=1;x<=n;++x) {
for(int y=1;y<=n;++y) {
bool flag1 = 1, flag2 = 1;
for(int i=1;i<=n;++i) {
flag1 &= (a[x][i] + a[y][i] <= K);
flag2 &= (a[i][x] + a[i][y] <= K);
}
if(flag1) Join(x, y);
if(flag2) Join(x+n, y+n);
}
}
int ans = 1;
for(int i=1;i<=2*n;++i) {
int fx = Find(i);
if(!vis[fx]) {
vis[fx] = 1; ans = (ans * alp[sz[fx]]) % mod;
}
}
printf("%lld
",ans);
return 0;
}
/*
3 13
3 2 7
4 8 9
1 6 5
12
*/
Summary
- 对于一个集合内元素可以互相交换,贡献即全排列