题意
给一个从1…n*m的被打乱的的网格图。
现在按顺序进行以下三种操作:
- 将每一行按某种顺序排列
- 将每一列按某种顺序排列
- 再将某一行按某种顺序排列
请构造一种方案让他回到按顺序排列的状态。
n<=100
思路
- 倒着推每次操作结束后的要求:
- 我们称末状态在第i行的元素为颜色i,那么操作2结束之后每一行都要是所对应的颜色。
- 那么操作1结束之后,要满足每一列都是1…n颜色的一个排列,才能使得操作2后每行颜色均归位。
- 现在问题变成了,如何安排操作1,使得上述条件满足。
- 考虑这个过程在做什么,每次将每一行中取出一个元素,要求每次都取出一个完整的排列,做m次。
- 不妨构造一个二分图,左边是每一行,右边是每一种颜色。连边 ( i , c o l o r [ i ] [ j ] ) (i, color[i][j]) (i,color[i][j])。
- 那么只需要找出m个完美匹配“相加”等于原图即可。
- 注意到这是一个n-正则二分图。根据Hall定理及其相关推论,直接使用dinic算法找m次完美匹配即可。
复杂度 O ( m 2 n n ) O(m^2n\sqrt n) O(m2nn)
关于Hall定理的一些描述:
- 对于一个二分图,若左测任意选一个集合S,右侧与其相连的点数不少于S,则此图必然有左侧的完美匹配。
- 一个n-正则二分图为每个点度数都是n的二分图。左边任选一个集合S,度数为n|S|,右侧至少相连 n ∣ S ∣ n = ∣ S ∣ \frac {n|S|} {n}=|S| nn∣S∣=∣S∣个点。因此一定有完美匹配。
- 一个n-正则二分图任意去掉一组完美匹配后,剩下的是一个(n-1)-正则二分图。
- 因此任意选择完美匹配都可以保证最后有解。
#include <bits/stdc++.h>
using namespace std;
const int N = 550;
int n,m;
int a[N][N], used[N][N];
int S, T, tot, to[N * N * 2], nex[N * N * 2], final[N * 2], f[N * N * 2];
int e[N][N], ans[N][N];
void _link(int x, int y, int fl) {
to[++tot] = y, nex[tot] = final[x], final[x] = tot;
f[tot] = fl;
}
void link(int x, int y, int fl) {
_link(x, y, fl), _link(y, x, 0);
}
int dis[N * 2];
void bfs() {
for(int i = 1; i <= T; i++) dis[i] = -1;
static int Q[N * 2]; int h = 0, t = 0;
Q[++t] = S;
dis[S] = 0;
while(h < t) {
int x = Q[++h];
for(int i = final[x]; i; i = nex[i]) if(f[i]) {
int y = to[i]; if (dis[y] == -1) {
dis[y] = dis[x] + 1;
Q[++t] = y;
}
}
}
}
int go(int x,int flow) {
if (x == T) return flow;
int used = 0;
for(int i = final[x]; i; i = nex[i]) if(f[i] && dis[x] + 1 == dis[to[i]]) {
int y = to[i];
int take = go(y, min(f[i], flow - used));
used += take;
f[i] -= take;
f[i ^ 1] += take;
if (used == flow) return used;
}
return used;
}
void work(int col) {
tot = 1;
memset(final, 0, sizeof final);
S = n + n + 1, T = S + 1;
for(int i = 1; i <= n; i++) {
link(S, i, 1);
for(int j = 1; j <= m; j++) if (!used[i][j]) {
e[i][j] = tot + 1;
link(i, n + 1 + (a[i][j] - 1) / m, 1);
}
}
for(int i = 1; i <= n; i++) {
link(n + i, T, 1);
}
while(bfs(), dis[T] != -1)
go(S, 1e9);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) if (!used[i][j]) {
if (f[e[i][j]] == 0) {
ans[i][col] = a[i][j];
used[i][j] = 1;
break;
}
}
}
int g[N];
int main() {
int gg = clock();
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
cin>>n>>m;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) scanf("%d", &a[i][j]), a[i][j] = (i - 1) * m + j;
}
for(int i = 1; i <= m; i++) {
cerr<<i<<endl;
work(i);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) printf("%d ",ans[i][j]);
printf("\n");
}
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) g[j] = ans[j][i];
sort(g + 1, g + 1 + n);
for(int j = 1; j <= n; j++) ans[j][i] = g[j];
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) printf("%d ",ans[i][j]);
printf("\n");
}
cerr<<clock()-gg<<endl;
}