二分图染色与匹配 题目推荐 + 刷题总结【車的放置和关押罪犯题解】

今天上了一下午的竞赛,学了关于二分图的知识。

和大家分享一下体会。

二分图很好用,感觉有了二分图一些想法能得到更好的实现了,以前光有思路却因为不会二分图而实现不了。

二分图其实很简单,说到底就是判定 + 匹配的运用。

判定用 染色法

匹配用 增广路算法(匈牙利算法)

拿 車的放置 举例:要求同行同列不能再次摆放物品。

题单推荐:

关押罪犯(洛谷)

棋盘覆盖(acwing) 

車的放置(洛谷)

导弹防御塔(洛谷)

难度依次为:绿 蓝 蓝 紫

截至最后一次编辑时间,完成了前3道题目。

做题思路:

关押罪犯:

二分答案+二分图染色

二分mid:这名市长看到的坏影响的至多为多少。

把仇恨大于mid的两人分到不同监狱

然后判断是否满足二分图,

如果是一个二分图说明可以这么分。

往左考虑最优解。

如果不是,说明这是不可行解。

往右寻找可行解。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
struct Edge{
    int from,to,w;
}e[N];
int n,m,l,r,tot,head[N],color[N];
void add(int x,int y,int z){//链式前向星存边 
	e[++tot].to=y;e[tot].from=head[x];e[tot].w=z;head[x]=tot;
}
bool judge(int mid){//二分答案 
    queue<int> q;
    memset(color,0,sizeof(color));
	for(int i=1;i<=n;i++){//染色法 
        if(!color[i]){
			q.push(i);
			color[i]=1;
            while(!q.empty()){
				int x=q.front();
				q.pop();      
				for(int i=head[x];i;i=e[i].from){		         		   
				   if(e[i].w>=mid){
					   if(!color[e[i].to]){  
							q.push(e[i].to);    	    		    		      		
							if(color[x]==1) color[e[i].to]=2;
							else color[e[i].to]=1; 
						}    		      	    	
						else if(color[e[i].to]==color[x])
							return 0;
					}
				}
			}
	    }
	}    
    return 1;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
    	int x,y,z;
    	scanf("%d%d%d",&x,&y,&z);
    	r=max(r,z);
    	add(x,y,z);
    	add(y,x,z);
	}
	r++;
    while(l<r){//保证可行解的基础上寻找最优解 
		int mid=(l+r)>>1;
		if(judge(mid)) r=mid;
		else l=mid+1;
	}
	if(l==0) printf(0);
    else printf("%d",l-1);
    return 0;
}

车的放置:

可以将问题转化为二分图匹配。

将矩阵的每一行和每一列分别看作二分图中的一组点。

如果某个位置可以选取,就在其所在行和列对应的节点之间连一条边。

这样每行只能匹配一个列节点,确保每行和每列最多选一个点。

所以问题的答案即为该二分图的最大匹配数。

#include<bits/stdc++.h>
using namespace std;
const int N=520,M=2e6+10;
int n,m,t,tot,ans;
int match[N],ver[M],Next[M],head[M];
bool z[N][N],vis[N];
void add(int x,int y){//链式前向星存边 
	ver[++tot]=y;Next[tot]=head[x];head[x]=tot;
}
bool dfs(int x){
	vis[x]=1;	
	for(int j=head[x];j;j=Next[j]){//为每一个"左"点匹配"朋友" 
		int y=ver[j];
		if(!vis[y]){
			vis[y]=1;
			if(!match[y] || dfs(match[y])){//右点没有"朋友"或者右点的"朋友"可以找另一个朋友 
				match[y]=x;//从而使左点可以与右点匹配 
				return 1;
			}
		}
	}
	return 0;
}
int main(){
	cin>>n>>m>>t;
	while(t--){
		int u,v;
		cin>>u>>v;
		z[u][v]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){//要遍历的是左部点集合与右部点集合的连边 因此不建双向边 
			if(!z[i][j])add(i,j+n);
		}
	}
	for(int i=1;i<=n;i++){//遍历左部点集合的连边 
		memset(vis,0,sizeof(vis));
		ans+=dfs(i);
	}
	cout<<ans<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值