二分图(最大匹配、最小点覆盖、最少边覆盖、最大独立集)

目录

P3386 【模板】二分图最大匹配

P1640 [SCOI2010]连续攻击游戏

P3355 骑士共存问题

P5030 长脖子鹿放置

P1525 [NOIP2010 提高组] 关押罪犯

373. 車的放置

374. 导弹防御塔

P2055 [ZJOI2009]假期的宿舍

P1129 [ZJOI2007] 矩阵游戏

P2319 [HNOI2006]超级英雄

P2016 战略游戏


P3386 【模板】二分图最大匹配

#include <bits/stdc++.h>
using namespace std;
int n, m, e, tot, u, v, ans, head[505], pre[505];	//pre[i]存的是右部图中点i所匹配的点 
bool vis[505];
//链式前向星存图 
struct node{
	int to;
	int next;
}edge[50010];
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
//找增广路径,判断能否给左部图中的点u找到一个匹配点 
bool find(int u)
{
	//遍历与点u相连的所有点(右部图中的点) 
	for(int i=head[u]; i!=0; i=edge[i].next){
		int v=edge[i].to;	//v就是右部图中与点u相连的点 
		//如果点v已被搜过 
		if(vis[v]==true)	continue;
		vis[v]=true;
		//如果点v还未被匹配,或者给点v原来匹配的点(pre[v])能找到新的匹配点 
		if(!pre[v] || find(pre[v])){ 
			pre[v]=u;
			return true;
		} 
	}
	return false;
}
int main()
{
	cin >> n >> m >> e;
	for(int i=1; i<=e; ++i){
		cin >> u >> v;
		add_edge(u, v);	//链式前向星存图,从左步图到右部图的一条有向边 
	}
	for(int i=1; i<=n; ++i){
		//置为false,表示所有点均未被搜过 
		memset(vis, 0, sizeof(vis));
		if(find(i))		//对左部点 i 找增广路,如果能找到,则匹配数加一 
			ans++;
	}
	cout << ans;	//输出最大匹配 
	return 0;
}

P1640 [SCOI2010]连续攻击游戏

#include <bits/stdc++.h>
using namespace std;
int n, u, v, tot, x, y, head[10010], match[1000010];    
bool vis[1000010];
//链式前向星存图 
struct node{
    int to;
    int next;
}edge[2000010];
void add_edge(int u, int v)
{
    tot++;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot;
}
//找增广路,为左部图中的点u在右部图中找匹配点 
bool find(int u) 
{
    //遍历与点u相连的点 
    for(int i=head[u]; i!=0; i=edge[i].next){
        v=edge[i].to;   //点v为与点u相连的点(在右部图中) 
        //如果点v在这一轮已被搜过 
        if(vis[v]==true)    continue;
        vis[v]=true;    //接下来要搜来,标记为当前时间戳,表示搜过了 
        if(!match[v] || find(match[v])){    //如果点v没有被匹配,或者能够给点v的匹配点重新找一个匹配点 
            match[v]=u; //将点u与点v匹配(u在左部图中,v在右部图中) 
            return true;    //return true 表示为点u找到了匹配点,说明有增广路,匹配数+1 
        }
    }   
    return false;   //当遍历完所有与点u相连的点后,仍然无法为点u寻找一个匹配,返回false 
} 
int main()
{
    cin >> n;
    for(int i=1; i<=n; ++i){
        cin >> x >> y;  //第i种装备的两个属性
        //因为属性的范围比装备的范围小,所以属性当做左部图,装备当做右部图 
        add_edge(x, i); 
        add_edge(y, i);
    }
    for(int i=1; i<=10000; ++i){   
        memset(vis, 0, sizeof(vis));
        if(find(i)==false){
            cout << i-1 << endl;
            return 0; 
        }
    }
    cout << 10000;
    return 0;
} 

P3355 骑士共存问题

#include <bits/stdc++.h>
using namespace std;
int n, m, tot, x, y, ans, id, head[40010], match[40010];
bool vis[210][210];	//有无障碍物 
int flag[40010];	//二分图最大匹配打标记 
int mx[8]={-1, -2, -2, -1, 1, 2,  2,  1};	//从10点钟方向开始 
int my[8]={-2, -1,  1,  2, 2, 1, -1, -2};	//从12点方向开始,最后一个点会TLE 
struct node{
	int to;
	int next;
}edge[160010];	//存边  
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
//找增广路 
bool find(int u, int id)
{
	for(int i=head[u]; i!=0; i=edge[i].next){
		int v=edge[i].to;
		if(flag[v]==id)	continue;	//时间戳 
		flag[v]=id;
		if(!match[v] || find(match[v], id)){
			match[v]=u;
			return true;
		}
	}
	return false;
}
int main()
{
	cin >> n >> m;
	for(int i=1; i<=m; ++i){
		cin >> x >> y;
		//(x,y)位置有障碍,不能放骑士,不能连边 
		vis[x][y]=true;
	}
	//有冲突的位置建边,待会寻找最小覆盖点(最大匹配),用节点数减去最小覆盖点就是最大独立点集 
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j){
			//(i,j)位置有障碍,这个点不能放骑士,不能连边,偶数点也不连边 
			if((((i+j)&1)==0) || vis[i][j]==true)	continue;
			//奇数点当左部图建边 
			for(int k=0; k<8; ++k){
				int xx=i+mx[k];
				int yy=j+my[k];
				//如果骑士能跳到的点在棋盘内,并且该点不是障碍物,则连边 
				if(xx>=1 && xx<=n && yy>=1 && yy<=n && vis[xx][yy]==false){
					add_edge((i-1)*n+j, (xx-1)*n+yy); 
				}
			}	 
		}
	}
	//求最小点覆盖(最大匹配) 
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j){
			id++;
			if(((i+j)&1)==1 && vis[i][j]==false){	//该点是奇数点,并且该点没有障碍物 
				if(find((i-1)*n+j, id)){
					ans++;
				}
			}	
		}
	} 
	 
	cout << n*n-m-ans;
	return 0;
} 

P5030 长脖子鹿放置

#include <bits/stdc++.h>
using namespace std;
int n, m, k, tot, x, y, ans, cnt, id, head[40010], match[40010];
bool vis[210][210];	//有无障碍物 
int flag[40010];	//二分图最大匹配打标记 
int mx[8]={-1, -3, -3, -1, 1, 3,  3,  1};	//从10点钟方向开始 
int my[8]={-3, -1,  1,  3, 3, 1, -1, -3};	
struct node{
	int to;
	int next;
}edge[320010];	//存边,因为奇数点到奇数点会建边,偶数点到偶数点也会建边,所以有200*200*8条边  
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
//找增广路 
bool find(int u, int id)
{
	for(int i=head[u]; i!=0; i=edge[i].next){
		int v=edge[i].to;
		if(flag[v]==id)	continue;	//时间戳 
		flag[v]=id;
		if(!match[v] || find(match[v], id)){
			match[v]=u;
			return true;
		}
	}
	return false;
}
int main()
{
	cin >> n >> m >> k;
	for(int i=1; i<=k; ++i){
		cin >> x >> y;
		//(x,y)位置有障碍,不能放长脖子鹿,不能连边 
		//可能多个障碍物在同一个位置,所以需要统计有多少个位置有障碍物 
		if(vis[x][y]==false){
			cnt++;
			vis[x][y]=true;
		}
	}
	//有冲突的位置建边,待会寻找最小覆盖点(最大匹配),用节点数减去最小覆盖点就是最大独立点集 
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=m; ++j){
			//(i,j)位置有障碍,这个点不能放长脖子鹿,不能连边 
			if(vis[i][j]==true)	continue; 
			for(int k=0; k<8; ++k){
				int xx=i+mx[k];
				int yy=j+my[k];
				//如果长脖子鹿能跳到的点在棋盘内,并且该点不是障碍物,则连边 
				if(xx>=1 && xx<=n && yy>=1 && yy<=m && vis[xx][yy]==false){
					add_edge((i-1)*n+j, (xx-1)*n+yy); 
				}
			}	 
		}
	}
	//求最小点覆盖(最大匹配) 
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=m; ++j){
			id++;
			if(vis[i][j]==false){	//该点没有障碍物 
				if(find((i-1)*n+j, id)){
					ans++;
				}
			}	
		}
	} 
	//因为奇数点和奇数点相连、偶书点和偶数点相连,计算最小覆盖时相当于多算了一遍 
	//所以最小覆盖为ans/2, 
	cout << n*m-cnt-ans/2;
	return 0;
} 

P1525 [NOIP2010 提高组] 关押罪犯

#include<bits/stdc++.h>
using namespace std;

const int N=20010;
const int M=100010;

int n, m, color[N];
bool flag;
vector<int> e[N];

struct Node{
    int x, y, v;
    bool operator < (const Node &other) const{
        return v < other.v;
    }
}a[M];

void dfs(int u, int c) {
    color[u]=c;
    for (int i=0; i < e[u].size(); i++){
        int v=e[u][i];
        if (!color[v]) dfs(v,3-c);
        else
        if (color[v]==c) flag=false;
    }
}

bool check(int pos) {
    for (int i=1; i <=n; i++) e[i].clear();
    for (int i=pos+1; i <=m; i++) {
        e[a[i].x].push_back(a[i].y);
        e[a[i].y].push_back(a[i].x);
    }
    flag=true;
    memset(color, 0, sizeof color);
    for (int i=1; i <=n; i++)
        if (!color[i]){
            dfs(i, 1);
            if (!flag) return false;
        }
    return true;
}

int main() {
    cin >> n >> m;
    for (int i=1; i <=m; i++)
        cin >> a[i].x >> a[i].y >> a[i].v;
    sort(a+1, a+1+m); 
    int l=0, r=m, mid; 
    while (l+1<r) {
        mid=(l+r) / 2;
        if (check(mid)) r=mid;
        else l=mid;
    }
    if(m==1) 	cout << "0";
    else cout << a[r].v;
    return 0;
}

373. 車的放置

#include <bits/stdc++.h>
using namespace std;
int n, m, t, x, y, ans, tot, head[201], match[201];	//match[i]表示右部点i匹配的左部点,为0表示还没有匹配 
//vis[i]表示对右部点i是否进行过一轮匹配查找, ban[i][j]为true表示(x,y)位置不能放置
bool vis[201], ban[201][201]; 
//链式前向星存图 
struct node{
	int to,next;
}edge[40001];
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
//寻找增广路,给左部点u找一个匹配 
bool find(int u)
{
	//遍历与u相连的右部点 
	for(int i=head[u]; i!=0; i=edge[i].next){
		int v=edge[i].to;
		if(!vis[v]){	//如果对右部点v没有进行过匹配查找 
			vis[v]=true;
			//如果右部点v还没有被匹配,或者右部点匹配的左部点还能找到新的匹配 
			if(!match[v] || find(match[v])){
				match[v]=u;	//将右部点v和左部点u匹配 
				return true;	//返回true,表示给左部点u找到了一个匹配 
			}
		}
	}	
	return false;
} 
int main()
{
	cin >> n >> m >> t;	//N行M列的棋盘,t个格子禁止放置。
	for(int i=1; i<=t; ++i){
		cin >> x >> y;
		ban[x][y]=true;	//(x,y)禁止放置車 
	}
	//行的编号作为点,充当左部图 
	for(int i=1; i<=n; ++i){
		//列的编号作为点,充当右部图 
		for(int j=1; j<=m; ++j){
			//如果位置(i, j)可以放車,则第i行和第j列连一条边
			//因为每一行每一列只能放一个車,相当于左部点和右部点中的点,最多都只能连一条边 
			if(!ban[i][j]){	
				add_edge(i, j);
			}
		}
	}
	for(int i=1; i<=n; ++i){
		memset(vis, 0, sizeof(vis));	
		if(find(i))
			ans++;
	}
	cout << ans;
	return 0;
}

374. 导弹防御塔

#include <bits/stdc++.h>
using namespace std;
int n, m, tot, head[2510], match[2510];
bool vis[2510];
double t1, t2, v, ans, x[2510], y[2510], x2[2510], y2[2510], f[51][51];
double cal(int i, int j)
{
	if(f[i][j]!=0)	return f[i][j];
	return f[i][j]=sqrt((x[i]-x2[j])*(x[i]-x2[j]) + (y[i]-y2[j])*(y[i]-y2[j]))/v;
}
struct node{
	int to;
	int next;
	double dis;
}edge[1250100];
void add_edge(int u, int v, double w)
{
	tot++;
	edge[tot].to=v;
	edge[tot].dis=w;
	edge[tot].next=head[u];
	head[u]=tot;
}
bool find(int u, double limit)
{
	for(int i=head[u]; i; i=edge[i].next){
		int vv=edge[i].to;
		double ww=edge[i].dis;
		if(vis[vv]==false && ww<=limit){
			vis[vv]=true; 
			if(!match[vv] || find(match[vv], limit)){
				match[vv]=u;
				return true;
			}
		}
	}
	return false;
}
bool check(double limit)
{
	//对于每次验证在limit的时限内能否拦截所有的入侵者
	//需要初始化match数组 
	memset(match, 0, sizeof(match));
	for(int i=1; i<=m; ++i){
		//每次找增广路时,需要初始化vis数组 
		memset(vis, 0, sizeof(vis));
		if(find(i, limit)==false){	//如果某个入侵者无法被拦截 
			return false; 
		}
	} 
	return true;	
} 
int main()
{
//	freopen("asd.txt", "r", stdin); 
	cin >> n >> m >> t1 >> t2 >> v;
	t1=t1/60;	//单位转换为分钟
	//入侵者的坐标 
	for(int i=1; i<=m; ++i){
		cin >> x[i] >> y[i];
	}
	//防御塔的坐标 
	for(int i=1; i<=n; ++i){
		cin >> x2[i] >> y2[i];
	}
	//建边,入侵者放在左部图,防御塔放在有部图,因为防御塔要扩展为多个 
	for(int i=1; i<=m; ++i){	//枚举入侵者,每一个入侵者要和每一座防御塔建边 
		for(int j=1; j<=n; ++j){	//枚举防御塔 
			for(int k=0; k<m; ++k){	//每座防御塔最多发射m次就可以打掉所有入侵者
				//入侵者的编号为i,防御塔的编号为j+k*n+j,其中k表示防御塔j第几次再发射 
				add_edge(i, j+k*n, t1+cal(i, j)+k*(t1+t2)); 
			} 
		}
	}
	//测试check()函数是否正确 
//	for(double i=0; i<=100; i=i+0.001){
//		if(check(i)==1){
//			printf("%.6lf", i);
//			break;
//		}
//	}
	//二分答案 
	double l=t1, r=100000000, mid;
	while(r-l>0.0000001){
		mid=(l+r)/2;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid;
		}
	}
	printf("%.6lf", mid);
	return 0;
}

P2055 [ZJOI2009]假期的宿舍

#include <bits/stdc++.h>
using namespace std;
int t, n, ans, cnt, tot, home[51], head[51], match[51]; 
bool stu[51], vis[51], know[51][51];
struct node{
	int to;
	int next;
}edge[1251];
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
bool find(int u) 
{
	for(int i=head[u]; i; i=edge[i].next){
		int v=edge[i].to;
		if(!vis[v]){
			vis[v]=true;
			if(!match[v] || find(match[v])){
				match[v]=u;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	cin >> t;
	while(t--){
		cin >> n;
		tot=0;		//清空链式前向星存的图 
		ans=0;		//能配对的人——床数 
		cnt=0;		//需要住宿的学生数 
		memset(head, 0, sizeof(head));
		memset(match, 0, sizeof(match));	//match[i]放的是i同学的床匹配给的同学 
		memset(stu, 0, sizeof(stu));		//是否是在校学生 
		memset(home, 0, sizeof(home));		//是否回家 
		memset(know, 0, sizeof(know));		//是否认识 
		//是否是在校学生,0表示不是,1表示是 
		for(int i=1; i<=n; ++i){
			cin >> stu[i];
		}
		//是否回家,0表示不回家,1表示回家,其他表示不是在校学生 
		for(int i=1; i<=n; ++i){
			cin >> home[i];
		}
		for(int i=1; i<=n; ++i){
			for(int j=1; j<=n; ++j){
				//第i个人和第j个人是否认识 
				cin >> know[i][j];
				//处理自己和自己认识 
				if(i==j)	know[i][j]=1;
			}
		}
		//建边,左部图为1到n个同学,右部图为1到n个同学
		//左边为准备入住的同学,右边为有床的同学 
		for(int i=1; i<=n; ++i){
			for(int j=1; j<=n; ++j){
				//i同学和j同学认识 并且 i同学不回家(在校生不回家或者外校同学),需要床 并且 j同学有床(是学校的学生) 
				if(know[i][j] && (stu[i]&&!home[i] || !stu[i]) && stu[j]){ //这个细节好坑啊
					add_edge(i, j);
				}
			}
		}
		for(int i=1; i<=n; ++i){
			memset(vis, 0, sizeof(vis));
			//如果左部点里的i同学没回家(在校生不回家或者外校同学),说明需要找匹配 
			if(stu[i]&&!home[i] || !stu[i]){	//这个细节好坑啊 
				cnt++;
				if(find(i)){
					ans++;
				}
				else{	//只要有个同学找不到床,接没必要继续找下去 
					break;
				} 
			}
		}
		if(ans==cnt){	//所有同学都有住的地方 
			cout << "^_^" << endl; 
		} 
		else{
			cout << "T_T" << endl; 
		}
	}
	return 0;
}

P1129 [ZJOI2007] 矩阵游戏

//和車的放置很像
#include <bits/stdc++.h>
using namespace std;
int t, n, x, ans, tot, head[201], match[201];
int flag[201];
struct node{
	int to;
	int next;
}edge[40001];
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
bool find(int u, int tag)
{
	for(int i=head[u]; i!=0; i=edge[i].next){
		int v=edge[i].to;
		if(flag[v]!=tag){
			flag[v]=tag;
			if(!match[v] || find(match[v], tag)){
				match[v]=u;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	cin >> t;
	while(t--){
		cin >> n;
		tot=0;
		ans=0;
		memset(match, 0, sizeof(match));
		memset(head, 0, sizeof(head));
		memset(flag, 0, sizeof(flag));
		//行号作为左部点, 列号作为右部点 
		for(int i=1; i<=n; ++i){
			for(int j=1; j<=n; ++j){
				cin >> x;
				if(x==1){
					add_edge(i, j);
				}
			}
		}
		for(int i=1; i<=n; ++i){
//			memset(vis, 0, sizeof(vis));
			if(find(i, i)){
				ans++;
			}
		}
		if(ans==n){
			cout << "Yes" << endl; 
		}
		else{
			cout << "No" << endl;
		}
	}
	return 0;
}

P2319 [HNOI2006]超级英雄

#include <bits/stdc++.h>
using namespace std;
int n, m, x, y, ans, tot, head[1001], vis[1001], match[1001], match1[1001];
bool link[1001][1001];
struct node{
	int to, next;
}edge[2002];
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
bool find(int u, int tag)
{
	for(int i=head[u]; i; i=edge[i].next){
		int v=edge[i].to;
		if(vis[v]!=tag){
			vis[v]=tag;
			if(!match[v] || find(match[v], tag)){
				match[v]=u;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	cin >> n >> m;
	for(int i=1; i<=m; ++i){
		cin >> x >> y;
		if(!link[i][x]){
			add_edge(i, x);
			link[i][x]=true;	
		}
		if(!link[i][y]){
			add_edge(i, y);
			link[i][y]=true;	
		}
	}
	//题目作为左部点, 妙计作为右部点 
	for(int i=1; i<=m; ++i){
		if(find(i, i))
			ans++;
		else
			break;
	}
	cout << ans << endl;
	for(int i=0; i<n; ++i){
		//第i个锦囊妙计解决第match[i]道题 
		if(match[i]!=-1){
			//match1[x]表示第x道题用的锦囊妙计 
			match1[match[i]]=i; 
		}
	}
	for(int i=1; i<=ans; ++i){
		cout << match1[i] << endl;
	}
	return 0;
}

P2016 战略游戏

//最小点覆盖问题=最大匹配 
#include <bits/stdc++.h>
using namespace std;
int n, k, tot, ans, x, y, head[1501], match[1501], vis[1501];
struct node{
	int to, next;
}edge[3000];	//双向建边, 需要为1500*2 
void add_edge(int u, int v)
{
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
bool find(int u, int tag)
{
	for(int i=head[u]; i; i=edge[i].next){
		int v=edge[i].to;
		if(vis[v]!=tag){
			vis[v]=tag;
			if(!match[v] || find(match[v], tag)){
				match[v]=u;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	cin >> n;
	for(int i=1; i<=n; ++i){
		cin >> x >> k;
		while(k--){
			cin >> y;
			//将编号变为1 
			add_edge(x+1, y+1); 
			add_edge(y+1, x+1); 
		}
	}
	for(int i=1; i<=n; ++i){
		if(find(i, i))
			ans++;
	}
	//无向图, 匈牙利算法算出的最大匹配是真实的两倍, 所以需要除以2 
	cout << ans/2;
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ypeijasd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值