看题面翻译,十分清楚。
1.分析
首先我们看到这两个交换的操作,分别定义为操作1和操作2。我们观察可以发现无论进行什么操作,原来同一列的元素操作完之后还是同一列,也就是说,对行的操作不会影响列的操作的进行,同理对列的操作不会影响行的操作的进行。
转眼一看数据范围:$n \le 50$ 基本可以确定是乱搞了。
2.思路
得到以上的结论后,我们可以将行与列分开考虑,即只操作行的答案乘上只操作列的答案就是最终的答案。
如何求只操作行(列)的答案呢?
我们可以暴力 $O(n^2)$ 枚举任意2行,然后暴力判断它能否互相交换。
现在我们可以发现一个事情:如果第A行和B行可以互相交换,B行和C行可以互相交换,则A行和C行显然也可以间接互相交换。于是我们设若干个行构成的集合(满足集合中每个行都可以互相直接或间接交换)为一个块。那么一个块中的行由于都可以互相交换,所以共有“数量的阶乘的阶乘”种排列方式。最后将所有块的排列方式乘起来就是只操作行的答案,我们。
用同样的方式我们可以求出只操作列的答案。
3.代码(有注释)
#include <iostream>
#include <cstring>
#define Mod 998244353
using namespace std;
const int maxn = 55;
int n, k, a[maxn][maxn], f[maxn], siz[maxn];
long long ans = 1, jc[maxn];
int find (int x) {
if (x == f)
return x;
else
return f = find (f);
}
bool ck1 (int x, int y) { // 检查两行是否可以互相直接交换
for (int i = 1; i <= n; i++)
if (a[i] + a[y][i] > k)
return false;
return true;
}
bool ck2 (int x, int y) { // 检查两列是否可以互相直接交换
for (int i = 1; i <= n; i++)
if (a[i] + a[i][y] > k)
return false;
return true;
}
void init () {
jc[1] = 1;
memset (siz, 0, sizeof (siz));
for (int i = 2; i <= maxn; i++)
jc[i] = (jc[i - 1] * i) % Mod; // 预处理阶乘
for (int i = 1; i <= n; i++)
f[i] = i;
return;
}
void solve () {
init ();
for (int i = 1; i <= n; i++) { // 求行的块
for (int j = i + 1; j <= n; j++) {
if (ck1 (i, j)) {
// 如果一个行与块中的任意一个行可以互相交换那么它必然可以和块中的任
// 意行交换
int fx = find (i), fy = find (j);
f[fy] = fx;
}
}
}
for (int i = 1; i <= n; i++)
siz[find (i)]++;
for (int i = 1; i <= n; i++)
if (siz[i])
ans = (ans * jc[siz[i]]) % Mod;
init ();
for (int i = 1; i <= n; i++) { // 求列的块
for (int j = i + 1; j <= n; j++) {
if (ck2 (i, j)) {
int fx = find (i), fy = find (j);
f[fy] = fx;
}
}
}
for (int i = 1; i <= n; i++)
siz[find (i)]++;
for (int i = 1; i <= n; i++)
if (siz[i])
ans = (ans * jc[siz[i]]) % Mod;
return;
}
int main () {
ios::sync_with_stdio (false);
std::cin.tie (0);
std::cout.tie (0);
cin >> n >> k;
memset (a, 127, sizeof (a));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
}
}
solve ();
cout << ans << endl;
return 0;
}