今天上了一下午的竞赛,学了关于二分图的知识。
和大家分享一下体会。
二分图很好用,感觉有了二分图一些想法能得到更好的实现了,以前光有思路却因为不会二分图而实现不了。
二分图其实很简单,说到底就是判定 + 匹配的运用。
判定用 染色法
匹配用 增广路算法(匈牙利算法)
拿 車的放置 举例:要求同行同列不能再次摆放物品。
题单推荐:
关押罪犯(洛谷)
棋盘覆盖(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;
}