二分图
染色法判断二分图
通过染色法可知,黑色和白色交替染色不冲突的话,被染色的环一定不含奇数环。
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N]; //染色
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool dfs(int u, int c)
{
color[u] = c;//先染上颜色
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(!color[j])//如果没有颜色往下找
{
if(!dfs(j, 3 - c)) return false; //如果发生冲突就返回false;
}
else if(color[j] == c)
return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
mst(h,-1);
while(m --)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bool flag = true;
for(int i = 1; i <= n; i++)
if(!color[i]) //如果没有颜色,就染色
{
if(!dfs(i, 1)) //如果产生冲突就break
{
flag = false;
break;
}
}
if(flag) puts("YES");
else puts("NO");
return 0;
}
匈牙利算法
找最大匹配数,例如一个男生A找一个女生B,如果女生有另一个男生C作为选择对象,那么再找C有没有其他合适的女生D进行选择,如果有C找D,A就找B,这样最大匹配数就变成了2。
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool find(int x)
{
for(int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if(!st[j])
{
st[j] = true;
if(match[j] == 0 || find(match[j]))
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
scanf("%d%d%d",&n1, &n2, &m);
mst(h, -1);
while(m --)
{
int a, b;
scanf("%d%d",&a, &b);
add(a, b);
}
int res = 0;
for(int i = 1; i <= n1; i++)
{
mst(st,false);
if(find(i))res ++;
}
printf("%d",res);
}
二分图最小点覆盖
二分图最小点覆盖 = 二分图最大匹配数
二分图最小边覆盖&&二分图最大独立集
二分图最大独立集 = 二分图最小边覆盖 = 点的个数 - 二分图最大匹配数
二分图带权最大匹配
KM算法
下面的内容借鉴此博客:https://www.cnblogs.com/gongpixin/p/5469040.html
有向无环图最小不相交路径覆盖
定义是用最少的不相交的路径覆盖所有顶点。每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
证明: 一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
因为路径之间不能有公共点,所以加的边之间也不能有公共点。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int N = 200 + 10;
vector<int> g[N];
int cy[N];
bool vis[N];
bool dfs(int u){
for(int i=0; i<g[u].size(); ++i){
int v = g[u][i];
if(vis[v]) continue;
vis[v] = true;
if(cy[v]==-1 || dfs(cy[v])){
cy[v] = u;
return true;
}
}
return false;
}
int solve(int n){
int ret = 0;
memset(cy, -1, sizeof(cy));
for(int i=1;i<=n;++i){
memset(vis, 0, sizeof(vis));
ret += dfs(i);
}
return n - ret;
}
int main() {
int t,n,m;
int u,v;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
g[i].clear();
for(int i=0;i<m;++i){
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
int ans = solve(n);
printf("%d\n",ans);
}
return 0;
}
有向无环图最小相交路径覆盖
先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。
证明: 为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int N = 500 + 10;
bool dis[N][N];
bool vis[N];
int cy[N];
void floyd(int n){
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=n;++k)
if(dis[i][k] && dis[k][j])//传递可达性
dis[i][j] = true;
}
bool dfs(int u, int n){
for(int i=1;i<=n;++i){
if(!vis[i] && dis[u][i]){
vis[i] = true;
if(cy[i]==-1 || dfs(cy[i], n)){
cy[i] = u;
return true;
}
}
}
return false;
}
int solve(int n){
int cnt = 0;
memset(cy,-1,sizeof(cy));
for(int i=1;i<=n;++i){
memset(vis,0,sizeof(vis));
cnt += dfs(i, n);
}
return n - cnt;
}
int main(int argc, const char * argv[]) {
int n,m;
int a,b;
while(scanf("%d%d",&n,&m),n+m){
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
dis[i][j] = false;
for(int i=1;i<=m;++i){
scanf("%d%d",&a,&b);
dis[a][b] = true;
}
floyd(n);
int ans = solve(n);
printf("%d\n",ans);
}
return 0;
}