1.二分图等价于不存在奇数环,用染色法染色不存在矛盾
2.匈牙利算法 匹配 最大匹配 匹配点 增广路径
匹配的定义:给定一个二分图(前提),如果其中一个边的集合M里面的所有边都没有公共点,那么就称这个边的集合为二分图的一个匹配。
这个二分图能够得到的拥有最多边的一个匹配就称为二分图的最大匹配
增广路径:从非匹配点走然后非匹配边然后匹配边然后非匹配然后匹配....最后是非匹配点
最大匹配等价于不存在增广路径
3.最小点覆盖 最大独立集 最小路径点覆盖( 最小路径重复点覆盖 )
最大匹配数=最小点覆盖=总点数-最大独立集=总点数-最小路径点覆盖
最小点覆盖:在图中,选出最少的点,使得每一边的两个点至少有一个点被选出来 中选最少的点
在二分图中最小点覆盖等于最大匹配数的
最大独立集:一个图中,选出最多的点,使得选出的点内部没有边
最大团:一个图中,选出最多的点,使得选出的点任意两点都有边
最大独立集与最大团互补
最大独立集等价于在原图中破环最少的点,将所有边都破坏掉
等价于 找最小点覆盖 等价于 找最大匹配
最小路径点覆盖: 对于一个有向无环图(DAG),用最少的互不相交的路径将所有点覆盖 简称最小路径覆盖 (匈牙利算法中还是用无向图)
最小路径重复点覆盖:给定一个DAG(有向无环图),选定最少的点,使得从这些点出发可以覆盖每一条路径(即每个点都经过至少一遍)。
最小路径重复点覆盖:求一遍原图的传递闭包,再求一遍最小路径点覆盖
4.最优匹配:给每个边一个权重的话 在达到最大匹配的情况下 所有边权的最大值是多少 用KM算法(最小费用流)
5.多重匹配问题:普通二分图每个点只能在一个边 而这个是每个点可以匹配多条边(最大流)
1.关押罪犯(二分+染色法)
(1)思路:二分[ 0,1e9 ] 取mid 然后整张图中只保留>mid的边 然后看看能不能把边的两个点分到两个不同的集合 如果可以mid就可行 可以看得出来这是满足二分性质的 所以本题就是二分+染色判二分图
(2)思路:并查集,但因为它们带有权值,所以我们先要把他排序,我们要尽可能让危害大的罪犯在两个监狱里(贪心)
1.首先我们把它门按照之间的影响值从大到小排序。
2.假设a与b是敌人,那么我们吧a,b分开放置,并且记录a的敌人是b,b的敌人是a
3.又假设a与c是敌人,这样的话我们记录一下c的敌人是a,这样的话,b和c就在一个监狱里,所以把他俩合并起来,怎么知道b与c在一个监狱呢,这是因为我们用rem数组记录了a的敌人,敌人的敌人是朋友,所以b,c合并
4.又说b和c是敌人,但是他俩已经被分到一个监狱了,所以直接输出他俩之间的影响值。
怎么证明他俩之间的影响值是最小的呢,因为我们之前已经按照权值排序了,已经尽可能不让权值大的在一起,所以这样输出是对的。
并查集代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct Node
{
ll x,y,w;
}a[N];
bool cmp(Node c,Node d)
{
return c.w>d.w;
}
int p[N],cnt[N];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
cin>>a[i].x>>a[i].y>>a[i].w;
sort(a+1,a+1+m,cmp);
for(int i=1;i<=m;i++)
{
int dx=find(a[i].x);
int dy=find(a[i].y);
if(dx==dy)
{
cout<<a[i].w<<endl;
return 0;
}
if(!cnt[a[i].x])
cnt[a[i].x]=a[i].y;
else
{
p[find(cnt[a[i].x])]=find(a[i].y);
}
if(!cnt[a[i].y]) cnt[a[i].y]=a[i].x;
else p[find(cnt[a[i].y])]=find(a[i].x);
}
cout<<0<<endl;
return 0;
}
二分+二分图
#include <bits/stdc++.h>
using namespace std;
const int N=20010,M=200010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int color[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int u,int c,int mid)
{
color[u]=c;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(w[i]<=mid) continue;
if(color[j])
{
if(color[j]==c) return false;
}
else if(!dfs(j,3-c,mid)) return false;
}
return true;
}
bool check(int mid)
{
memset(color,0,sizeof color);
for(int i=1;i<=n;i++)
if(color[i]==0)
if(!dfs(i,1,mid)) return false;
return true;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
int l=0,r=1e9;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
2.棋盘覆盖
思路:把相邻的两个单元格看成一个点 然后连上一条边 那么我们要求的答案就是最多可以一对一匹配多少个边 那么就是最大匹配问题了 然后再看看是不是二分图 因为这只是最大匹配问题 但是现在还没有说和二分图有关系 匈牙利算法解决的是二分图的最大匹配
可以看下图这样染色 我们连的边一定是一个白格子一个绿格子 所以是二分图
并且绿色格子都是偶数 白色是奇数
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=110;
int n,m;
bool g[N][N];
bool st[N][N];
PII match[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
bool dfs(int x,int y)
{
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<1||a>n||b<1||b>n)
continue;
if(st[a][b]||g[a][b]) continue;
st[a][b]=true;
PII t=match[a][b];
if(t.x==0||dfs(t.x,t.y))
{
match[a][b]={x,y};
return true;
}
}
return false;
}
int main()
{
cin>>n>>m;
while(m--)
{
int x,y;
cin>>x>>y;
g[x][y]=true;
}
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if((i+j)%2&&!g[i][j])
{
memset(st,0,sizeof st);
if(dfs(i,j))res++;
}
cout<<res<<endl;
return 0;
}
3.机器任务
最小点覆盖
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,k;
bool g[N][N],st[N];
int match[N];
bool find(int x)
{
for(int i=1;i<m;i++)
{
if(!st[i]&&g[x][i])
{
st[i]=true;
int t=match[i];
if(t==0||find(t))
{
match[i]=x;
return true;
}
}
}
return false;
}
int main()
{
while(cin>>n,n)
{
cin>>m>>k;
memset(g,0,sizeof g);
memset(match,0,sizeof match);
while(k--)
{
int t,a,b;
cin>>t>>a>>b;
if(!a||!b) continue;
g[a][b]=true;
}
int res=0;
for(int i=1;i<n;i++)
{
memset(st,0,sizeof st);
if(find(i)) res++;
}
cout<<res<<endl;
}
return 0;
}
4. 骑士放置(最大独立集)
把所有可以攻击到的马连条边,则答案就是求两两不能攻击到的马,也即最大独立集的问题
证明是二分图可以用刚刚的那种染色方法
答案就是n*m-k-最大匹配数 总点数-坏掉的-最大匹配
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=110;
int n,m,k;
PII match[N][N];
bool g[N][N];
bool st[N][N];
int dx[8]={-2,-1,1,2,2,1,-1,-2};
int dy[8]={1,2,2,1,-1,-2,-2,-1};
bool find(int x,int y)
{
for(int i=0;i<8;i++)
{
int a=x+dx[i];
int b=y+dy[i];
if(a<1||a>n||b<1||b>m) continue;
if(g[a][b]||st[a][b]) continue;
st[a][b]=true;
PII t=match[a][b];
if(t.x==0||find(t.x,t.y))
{
match[a][b]={x,y};
return true;
}
}
return false;
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<k;i++)
{
int x,y;
cin>>x>>y;
g[x][y]=true;
}
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if((i+j)%2||g[i][j]) continue;
memset(st,0,sizeof st);
if(find(i,j)) res++;
}
cout<<n*m-k-res<<endl;
return 0;
}
5.捉迷藏(最小路径重复点覆盖)
题目就是从任意一个点出发,都不能走到另外一个点的最大数量,答案就是最小路径重复点覆盖
#include<bits/stdc++.h>
using namespace std;
const int N=210;
bool g[N][N],st[N];
int match[N];
int n,m;
bool find(int x)//帮x找女朋友
{
for(int i=1;i<=n;i++)//枚举可能的女朋友
if(!st[i]&&g[x][i])//假如这个女的没男朋友并且我跟他有关系
{
st[i]=true;//标记这个女的找到了男朋友
int t=match[i];
if(t==0||find(t))//假如这个女的没男朋友或着那男的可以换个女朋友
{
match[i]=x;//则把这个女的让给我
return true;//返回找到了
}
}
return false;//反之找不到
}
int main()
{
cin>>n>>m;
while(m--)
{
int a,b;
cin>>a>>b;
g[a][b]=true;
}
//做一遍传递闭包
for(int k=1;k<=n;k++)//floryd求传递闭包
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]|=g[i][k]&g[k][j];
int res=0;
for(int i=1;i<=n;i++)
{
memset(st,0,sizeof st);//清空
if(find(i)) res++;//假如找的到女朋友
}
cout<<n-res<<endl;//输出最大路径重复点覆盖
return 0;
}
然后其实给st加一个时间戳 不用每次都memset
#include<bits/stdc++.h>
using namespace std;
const int N=210;
bool g[N][N];
int st[N];
int match[N];
int n,m;
bool find(int x,int tmp)//帮x找女朋友
{
for(int i=1;i<=n;i++)//枚举可能的女朋友
if(st[i]!=tmp&&g[x][i])//假如这个女的没男朋友并且我跟他有关系
{
st[i]=tmp;//标记这个女的找到了男朋友
int t=match[i];
if(t==0||find(t,tmp))//假如这个女的没男朋友或着那男的可以换个女朋友
{
match[i]=x;//则把这个女的让给我
return true;//返回找到了
}
}
return false;//反之找不到
}
int main()
{
cin>>n>>m;
while(m--)
{
int a,b;
cin>>a>>b;
g[a][b]=true;
}
//做一遍传递闭包
for(int k=1;k<=n;k++)//floryd求传递闭包
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]|=g[i][k]&g[k][j];
int res=0;
for(int i=1;i<=n;i++)
{
//memset(st,0,sizeof st);//清空
if(find(i,i)) res++;//假如找的到女朋友
}
cout<<n-res<<endl;//输出最大路径重复点覆盖
return 0;
}