poj-3041

31 篇文章 0 订阅
17 篇文章 0 订阅
//1380K	47MS	G++
#include <stdio.h>
#include <string.h>

#define MAX 510

int N;
int K;

int Vset1[MAX]; // row
int Vset2[MAX]; // column
int G[MAX][MAX]; // 0: no asteriod, 1: asteriod
int assignedFlag[MAX];
int waitingMove[MAX];

char getPair(int beginId) {
	// printf("getPair %d\n", beginId);
	for (int i = 1; i <= N; i++) { // find its adjancey
		if (G[beginId][i] > 0) { // if connected
			if (!assignedFlag[i]) { // if not assigned to other yet
				Vset1[beginId] = i;
				Vset2[i] = beginId;
				assignedFlag[i] = 1;
				return 1;
			} else { // assigned to other before, try to move other to another
				waitingMove[beginId] = 1; // beginId is waiting for move
				// if i's owner is not waitting move, try move it.
				if (!waitingMove[Vset2[i]] && getPair(Vset2[i])) { // if move others OK
					Vset1[beginId] = i;
					Vset2[i] = beginId;
					assignedFlag[i] = 1;
					return 1;
				} else { // if move other failed, try next connected Node
					continue;
				}
			}
		}
	}
	return 0;
}

void getMinShot() {
	int shotTime = 0;

	for (int i = 1; i <= N; i++) {
		memset(waitingMove, 0, sizeof(waitingMove));
		if (getPair(i)) {
			shotTime++;
		}
	}

	printf("%d\n", shotTime);
}

int main() {
	while (scanf("%d %d", &N, &K) != EOF) {
		memset(Vset1, 0, sizeof(Vset1));
		memset(Vset2, 0, sizeof(Vset2));
		memset(G, 0, sizeof(G));
		memset(assignedFlag, 0, sizeof(assignedFlag));
		for (int i = 0; i < K; i++) {
			int x;
			int y;
			scanf("%d %d", &x, &y);
			G[x][y] = 1;
		}
		getMinShot();		
	}
	
}


二分图的基础应用题。

不过对于第一次用的我来说,光是转化就费了一番周折,

一开始是想这样划分两个点集:

第一个点集V1是坐标系中每一行和每一列(分别代表着一次shot的位置)

第二个点集V2是坐标系中的每一个asteroid,

之间的连线也就是边则代表着在此行/列shot,边另一端的的ateroid就可以被消灭。

但是这样划分以后,却发现不管是二分图的最小点覆盖还是最小边覆盖,都不能得到题目想要的解,

因为题目要的解是一个边的集合S :边的起点都在V1,而终点都在V2,并且v2中所有的点都能被S的边所覆盖,同时要求S在V1中覆盖的点最少。

这样当然最小点/边覆盖都不行,因为这两者会覆盖所有的边/点.

后来查了下才发现自己想偏了,应该这样划分两个点集:

V1代表每一行的shot

V2代表每一列的shot

而每一个asteroid(x , y)则是连接 y行 与 x列的的边, 该边代表着此asteriod被消灭。

这样,所有asteriod都被消灭就是指此二分图的所有边的集合S,

而要求的最少shot则是覆盖所有边的最小点集, 这样就转化成了最小点覆盖了(=二分图的最大匹配)。

之前用过二分图,果然威力还是比较强大的,顺便总结一下二分图最大匹配的匈牙利算法:

http://blog.csdn.net/dark_scope/article/details/8880547

这个总结的非常好,虽然没讲原理和证明,但是对于算法流程足够了。

首先,要有两个数组V1和V2分别代表着之前提到的二分图的两个点集。

首先遍历V1的每个Node i,做这个操作: getPair(i):

根据图G的连通信息来遍历于i邻接的V2中的点,

如果这样的点集T,那么遍历点T的每个点j:

case1: 如果j之前没有被分配给其他V1的点,那么就将j分配给i,更新相应的数组V1[i] = j, V1[j] = i, 直接返回1代表成功

case2: 如果j之前已经被分配给其他点k了,那么做这样的尝试:

为k在它的V2邻接点中找一个点分配给它,这时候就是一个递归调用getPair(k)了

这个递归调用会有一个隐含问题,就是无限死锁递归,最终segment fault , 比如 i -> getPair(k), k-> getPair(i),那么就绝对无限递归了。

因此,每次在对Node i进行配对时,要搞一个flag数组F,F[i] 标示 i 是否正在等待其他的V1的点k腾出位置, 所以要在上面的操作加一个判断,

        如果F[k]标示在之前的递归调用 k 就已经在等待别的V1的点腾位置,这时候如果继续getPair(k), 那么就会无限调用了,所以要打破死锁,直接返回0

标示不能腾出位置来,如果能腾,那么更新相应的V1[i] 和V2[j],直接返回1代表成功。

如果根本没有邻接点或者找不到能和i配对的V2点,直接return 0代表失败。


对于V1的每个点i,getPair()返回成功就把最大匹配的值加1.

最后返回最大匹配值就可以了,最小覆盖点的点数量和它相等.

二分图的原理和证明还需要好好看看,光知道怎么写在遇到变种题和转化难度高的题时还是很吃力的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值