题意简述:有一个\(n\times m\)的矩阵\(a\),\([1, n\cdot m]\)的整数恰好在其中出现一次。我们需要执行以下三次操作:
- 将\(a\)的每一行\(m\)个数任意变换顺序,得到矩阵\(b\)。
- 将\(b\)的每一列\(n\)个数任意变换顺序,得到矩阵\(c\)。
- 将\(c\)的每一行\(m\)个数任意变换顺序,得到矩阵\(d\)。
其中,矩阵\(d\)满足\(d_{i, j}=(i-1)\cdot m+j\)。要求输出矩阵\(b\)和\(c\)。\(1\leq n, m\leq 100\)。
考虑由矩阵\(d\)倒推回\(c, b\)。由于\(c\rightarrow d\)的过程只能任意变换每一行,因此\(c\)满足第\(i\)行构成一个\([(i-1)\cdot m+1, i\cdot m]\)的排列。
同样由于\(b\rightarrow c\)的过程只能任意变换每一列,因此\(b\)满足对于每一列\(j\),\(\lfloor\frac{b_{i, j}-1}{m}\rfloor\)构成一个\([0, n-1]\)的排列。
考虑\(a\rightarrow b\)的过程,发现我们只关心\(\lfloor\frac{a_{i, j}-1}{m}\rfloor\)的值,可以用它代替\(a_{i, j}\)。从左到右依次考虑每一列,发现我们只需要在每一行\(i\)找到一个\(x_i\),使得\(x\)构成一个\([0, n-1]\)的排列即可。
发现可以使用二分图匹配来寻找\(x\)。令左部点\(u\)为\([0, n-1]\),右部点\(v\)为每一行,\(u_i\)与\(v_j\)之间有\(w\)条边,当且仅当第\(j\)行剩余了\(w\)个元素\(i\)。
正确性证明:由于初始\([0, n-1]\)每个数都只出现\(m\)次,且每次取走的\(x\)都是一个排列,因此对于任意的\(i\in[1, m]\),第\(i\)次匹配时,每行剩余的元素个数和每个元素出现的次数均为\(m-i+1\),即,每个点的度数恰好为\(m-i+1\)(重边算多次)。
对于任意的左部点集\(S\),它的度数和为\(|S|\cdot(m-i+1)\),右部点至少需要\(|S|\)个才能装下这些度数,反之同理。因此任意同侧点集\(S\),有\(|\rm{neighbour}(S)|\geq |S|\),根据Hall定理,必然存在完美匹配。
构造\(b\)之后,\(c\)的构造显然。使用dinic进行二分图匹配,复杂度\(O(n^3\sqrt{n})\)。
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cassert>
#include <iostream>
#include <algorithm>
#define R register
#define ll long long
using namespace std;
const int N = 210, M = 21000, inf = 1e9;
int n, m, tab[N][N], hd[N], nxt[M], cap[M], to[M], noedg, used[N][N], col[N][N], S, T, b[N][N];
int c[N][N], lev[N], cur[N];
queue<int> que;
template <class T> inline void read(T &x) {
x = 0;
char ch = getchar(), w = 0;
while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = w ? -x : x;
return;
}
inline bool bfs() {
memset(lev, 0, sizeof (lev));
memcpy(cur, hd, sizeof (cur));
lev[S] = 1, que.push(S);
while (que.size()) {
int now = que.front();
que.pop();
for (R int i = hd[now], v; i; i = nxt[i])
if (!lev[v = to[i]] && cap[i])
lev[v] = lev[now] + 1, que.push(v);
}
return lev[T];
}
int dfs(int now, int flow) {
if (now == T || !flow) return flow;
int ret = 0;
for (R int &i = cur[now], v; i; i = nxt[i]) {
if (!cap[i] || lev[v = to[i]] != lev[now] + 1) continue;
int fl = dfs(v, min(flow, cap[i]));
cap[i] -= fl, cap[i ^ 1] += fl, ret += fl, flow -= fl;
if (!flow) break;
}
return ret;
}
inline int dinic() {
int ret = 0;
while (bfs()) ret += dfs(S, inf);
return ret;
}
void addEdg(int x, int y, int w) {
nxt[++noedg] = hd[x], hd[x] = noedg, to[noedg] = y, cap[noedg] = w;
nxt[++noedg] = hd[y], hd[y] = noedg, to[noedg] = x, cap[noedg] = 0;
return;
}
int main() {
read(n), read(m);
for (R int i = 1; i <= n; ++i)
for (R int j = 1; j <= m; ++j)
read(tab[i][j]), --tab[i][j], ++col[i][tab[i][j] / m];
S = 2 * n + 1, T = S + 1;
for (R int j = 1; j <= m; ++j) {
noedg = 1;
for (R int i = 1; i <= T; ++i)
hd[i] = 0;
for (R int i = 1; i <= n; ++i) {
addEdg(S, i, 1), addEdg(i + n, T, 1);
for (R int c = 1; c <= n; ++c)
if (col[i][c - 1])
addEdg(i, c + n, 1);
}
dinic();
for (R int i = n + 1; i <= (n << 1); ++i) {
for (R int e = hd[i], v; e; e = nxt[e]) {
if ((v = to[e]) <= n && cap[e]) {
for (R int k = 1; k <= m; ++k)
if (tab[v][k] / m == i - n - 1 && !used[v][k]) {
b[v][j] = tab[v][k], used[v][k] = 1, --col[v][i - n - 1];
break;
}
break;
}
}
}
}
for (R int i = 1; i <= n; ++i) {
for (R int j = 1; j <= m; ++j)
printf("%d ", b[i][j] + 1);
printf("\n");
}
for (R int j = 1; j <= m; ++j) {
for (R int i = 1; i <= n; ++i) {
for (R int k = 1; k <= n; ++k)
if (b[k][j] / m == i - 1) {
c[i][j] = b[k][j];
break;
}
}
}
for (R int i = 1; i <= n; ++i) {
for (R int j = 1; j <= m; ++j)
printf("%d ", c[i][j] + 1);
printf("\n");
}
return 0;
}
原文地址:https://www.cnblogs.com/suwakow/p/11675289.html