agc037 D Sorting a Grid (二分图匹配)

题意

给一个从1…n*m的被打乱的的网格图。
现在按顺序进行以下三种操作:

  1. 将每一行按某种顺序排列
  2. 将每一列按某种顺序排列
  3. 再将某一行按某种顺序排列

请构造一种方案让他回到按顺序排列的状态。
n<=100

思路

  • 倒着推每次操作结束后的要求:
  • 我们称末状态在第i行的元素为颜色i,那么操作2结束之后每一行都要是所对应的颜色。
  • 那么操作1结束之后,要满足每一列都是1…n颜色的一个排列,才能使得操作2后每行颜色均归位。
  • 现在问题变成了,如何安排操作1,使得上述条件满足。
  • 考虑这个过程在做什么,每次将每一行中取出一个元素,要求每次都取出一个完整的排列,做m次。
  • 不妨构造一个二分图,左边是每一行,右边是每一种颜色。连边 ( i , c o l o r [ i ] [ j ] ) (i, color[i][j])
  • 那么只需要找出m个完美匹配“相加”等于原图即可。
  • 注意到这是一个n-正则二分图。根据Hall定理及其相关推论,直接使用dinic算法找m次完美匹配即可。
    复杂度 O ( m 2 n n ) O(m^2n\sqrt n)

关于Hall定理的一些描述:

  • 对于一个二分图,若左测任意选一个集合S,右侧与其相连的点数不少于S,则此图必然有左侧的完美匹配。
  • 一个n-正则二分图为每个点度数都是n的二分图。左边任选一个集合S,度数为n|S|,右侧至少相连 n S n = S \frac {n|S|} {n}=|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;
}
发布了266 篇原创文章 · 获赞 93 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/102725307