二分图的应用:最小覆盖,最小边覆盖,最大独立子集,最小路径覆盖

最小覆盖:选择尽量少的点,使得每条边至少有一个端点被选中;最小覆盖 = 最大匹配数;

poj3041最小覆盖;

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<set>
#include<stack>
#include<map>
#define ll long long
#define MAX 1000
#define INF INT_MAX
#define eps 1e-8

using namespace std;

int Left[MAX],n;
bool T[MAX];

vector<int>G[MAX];

bool match(int u){
	for (int i = 0; i<G[u].size(); i++) if (!T[G[u][i]]){
		int v = G[u][i];
		T[v] = true;
		if (!Left[v] || match(Left[v])){
			Left[v] = u;
			return true;
		}
	}
	return false;
}

int mark[MAX];

int KM(){
	int cnt = 0;
	memset(Left,0,sizeof(Left));
	for (int i = 1; i<=n; i++) if (mark[i]){
		memset(T,0,sizeof(T));
		if (match(i)) cnt++;
	}
	return cnt;
}

int main(){
	int m;
	while (scanf("%d%d",&n,&m) != EOF){
		memset(mark,0,sizeof(mark));
		for (int i = 0; i<=n; i++) G[i].clear();
		int u,v;
		for (int i = 1; i<=m; i++){
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			mark[u] = 1;
		}
		printf("%d\n",KM());
	}
	return 0;
}



最小边覆盖:用最少的边覆盖所有的点;最小边覆盖 = 节点数 - 最大匹配数;

poj3020最小边覆盖;

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<string>
#define ll long long
#define MAX 500
#define INF INT_MAX
#define eps 1e-8

using namespace std;

int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};

char s[MAX][MAX];

int Left[MAX],n;
bool T[MAX];
vector<int>G[MAX];

bool match(int u){
	for (int i = 0; i<G[u].size(); i++) if (!T[G[u][i]]){
		int v = G[u][i];
		T[v] = true;
		if (!Left[v] || match(Left[v])){
			Left[v] = u;
			return true;
		}
	}
	return false;
}

int mark[MAX];

int KM(){
	int cnt = 0;
	memset(Left,0,sizeof(Left));
	for (int i = 0; i<=n; i++) if (mark[i]){
		memset(T,0,sizeof(T));
		if (match(i)) cnt++;
	}
	return cnt;
}

int main(){
	int cas,x,y;
	scanf("%d",&cas);
	while (cas--){
		memset(mark,0,sizeof(mark));
		int cnt = 0;
		scanf("%d%d",&x,&y);
		n = x*y;
		for (int i=0; i<=n; i++) G[i].clear();
		for (int i = 1; i<=x; i++)  scanf("%s",s[i]+1);
		for (int i = 1; i<=x; i++){
			for (int j = 1; j<=strlen(s[i]+1); j++ ) if (s[i][j] == '*'){
				mark[(i-1)*y + j] = 1;
				cnt++;
				for (int k = 0; k<4; k++){
					int ex = i + dx[k];
					int ey = j + dy[k];
					if (ex >= 1 && ex <= x && ey >=1 && ey <= y && s[ex][ey] == '*') G[(i-1)*y + j].push_back((ex-1)*y + ey);
				}
			}
		}
		printf("%d\n",cnt - KM()/2);
	}
	return 0;
}


最大独立子集:对于没一条边,至少有一个点不被选中;最大独立子集 = 节点数 - 最大匹配数;

注意:最大独立子集,常常需要去从对立面考虑,即可能需要将没有关系的节点连一条边,然后求最大独立子集,即为结果

poj3692 最大独立子集;

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
#include<string>
#include<stack>
#define ll long long
#define MAX 1000
#define eps 1e-8
#define INF INT_MAX

using namespace std;

int w[MAX][MAX],Left[MAX],n,m;     //Left[i]记录与右边的节点i所匹配的左边的节点 (可以根据Left[]找出一组最大匹配);n、m分别表示左右节点的个数 

bool S[MAX],T[MAX];   //记录右边的节点是否被访问过 

bool match(int u){ 
	S[u] = true;            //从左边的节点u出发找增广路 
	for (int i = 1; i<=m; i++) if (!T[i] && w[u][i]){
		T[i] = true;
		if (!Left[i] || match(Left[i])){
			Left[i] = u;
			return true;
		}
	}
	return false;
}

int KM(){
	int cnt = 0;
	memset(Left,0,sizeof(Left));
	for (int i=1; i<=n; i++){
		memset(T,0,sizeof(T));
		memset(S,0,sizeof(S));
		if (match(i)) cnt++;    //如果找到增广路,则说明匹配数目增加1 
	}
	return cnt;
}

int mark[MAX][MAX];

int main(){
	int M,cas = 0;
	while (scanf("%d%d%d",&n,&m,&M) && !(n == 0 && m == 0 && M == 0)){
		cas++;
		int u,v;
		memset(mark,0,sizeof(mark));
		memset(w,0,sizeof(w));
		for (int i = 1; i<=M; i++){
			scanf("%d%d",&u,&v);
			mark[u][v] = 1;
		}
		for (int i = 1; i<=n; i++){
			for (int j = 1; j <= m; j++)
				if (!mark[i][j]) w[i][j] = 1;
		}
		int ans = KM();
	//	for (int i = 1; i<=n; i++) if (S[i]) printf("%d ",i); printf("\n");
	//	for (int i = 1; i<=m; i++) if (T[i]) printf("%d ",i); printf("\n");
	//	for (int i = 1; i<=m; i++) printf("%d ",Left[i]); printf("\n");
		printf("Case %d: %d\n",cas,n+m - ans);
	}
	return 0;
}



DAG最小路径覆盖:在图中找尽量少的路径,使得每个节点恰好在一条路径上;

解法:把所有节点拆违X节点i,和Y节点i',如果原图中有边i->j,则而二分图中引入边i->j',则节点数(原图中节点数n) - 最大覆盖数 = 最小路径覆盖的路径数;

poj2060最小路径覆盖;

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<string>
#include<stack>
#include<map>
#define ll long long
#define MAX 1000
#define INF INT_MAX
#define eps 1e-8

using namespace std;

using namespace std;

int Left[MAX],n;
bool T[MAX];

vector<int>G[MAX];

bool match(int u){
	for (int i = 0; i<G[u].size(); i++) if (!T[G[u][i]]){
		int v = G[u][i];
		T[v] = true;
		if (!Left[v] || match(Left[v])){
			Left[v] = u;
			return true;
		}
	}
	return false;
}

int KM(){
	int cnt = 0;
	memset(Left,0,sizeof(Left));
	for (int i = 0; i<n; i++){
		memset(T,0,sizeof(T));
		if (match(i)) cnt++;
	}
	return cnt;
}

struct Node{
	int time,a,b,c,d;
}p[MAX];
 
bool judge(int i, int j){
	int end = p[i].time + abs(p[i].a- p[i].c) + abs(p[i].b - p[i].d) + abs(p[i].c - p[j].a) + abs(p[i].d - p[j].b) + 1;
	if (end <= p[j].time) return true;
	return false;
}
 
int main(){
	int cas;
	scanf("%d",&cas);
	while (cas--){
		scanf("%d",&n);
		int u,v;
		for (int i = 0; i<=n; i++) G[i].clear();
		for (int i = 1; i<=n; i++){
			scanf("%d:%d",&u,&v);
			p[i].time = u*60 + v;
			scanf("%d%d%d%d",&p[i].a,&p[i].b,&p[i].c,&p[i].d);
		}
		for (int i = 1; i<=n; i++){
			for (int j = 1; j<=n; j++){
				if (judge(i,j)) G[i].push_back(j);
			}
		}
		printf("%d\n",n - KM());
	}
	return 0;
}


注意:在用二分图最大匹配模型来解决问题是,建模是关键;图中没有天然的二分图时,常需要将每个点拆为X节点,和Y节点,然后建图,这样求的的最大匹配数为原图中的最大匹配数的2倍(相当于每条边被算了两次)。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值