二分图 匈牙利算法

  • 二分图
    二分图    ⟺    \iff 染色法    ⟺    \iff 图中不存在奇数环,每一个都是充分必要条件
  • 二分图的性质:难的不是写出二分图的算法,是看出这道题是有关二分图的,一般的二分图也就是染色法来判断改图是否是二分图,一般是DFS,
  • 匈牙利算法:求解二分图的最大匹配问题,扩展:最大匹配的权值问题就是KM算法
  • 建立二分图:有时候题目给点不是直接给的,有可能直接给你一个图,然后你需要的就是从图中,看出是二分图
  • 最小点覆盖:用最少的点,覆盖所有的边
  • 最大匹配: 尽量让每个点都有一个非公共的匹配点
  • 最大独立集: 选出最多的点,使得选出的点之间没有边
  • 重点:
    在二分图中最小点覆盖 = 最大匹配数 = 总点数 - 最大独立集 = 总点 - 最小路径覆盖
  • 例如 Acwing 棋盘覆盖:抽离出点和边就是一个二分图
  • 代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#define fi first
#define se second

using namespace std;
const int N = 110;

typedef pair<int, int> PII;

PII match[N][N];
int n,m; 
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool st[N][N],g[N][N];

bool find(int x,int y)
{
    for(int i = 0;i < 4;i ++ )
    {
        int x1 = x + dx[i],y1 = y + dy[i];
        if(x1 < 1 || y1 < 1 || x1 > n || y1 > n) continue;
        if(!st[x1][y1] && !g[x1][y1])
        {
            st[x1][y1] = true;
            PII t = match[x1][y1];
            if(t.fi == 0 || find(t.fi,t.se))
            {
                match[x1][y1] = {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 ++ )
        {
            memset(st, 0, sizeof st);
            if(i + j & 1 && !g[i][j])
            {
                if(find(i,j)) res++;
            }
        }
    }
    
    cout << res << endl;
    return 0;
}
  • 洛谷 封锁阳光大学: 搜索 + 二分图
#include <bits/stdc++.h>
//#define int long long 
#define ios ios::sync_with_stdio(0),cin.tie(0)
#define fi first
#define se second
#define pb push_back
#define PII pair<int,int>
using namespace std;

const int N = 1e4 + 100,M = 2e5 + 100,INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], idx;

int n,m; 
int lx[N],ly[N];
int color[N];
bool st[N];

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int bfs(int u)
{
    queue<int> q;
    q.push(u);
    color[u] = 1;
    int d = 0,d2 = 0;
    while(q.size())
    {
        int t = q.front(); q.pop();
        
        if(color[t] == 1) d++;
        else if(color[t] == 2) d2++;
        
        for(int i = h[t];i != -1;i = ne[i])
        {
            int j = e[i];
            if(color[j] == color[t]) return -1;
            else if(color[j] == 0)
            {
                color[j] = 3 - color[t];
                q.push(j);
            }
        }
    }
    
    int w = min(d,d2);
    return w;
}

void solve()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for(int i = 1;i <= m;i ++ )
    {
        int u,v; cin >> u >> v;
        add(u,v),add(v,u);
    }
    int sum = 0;
    for(int i = 1;i <= n;i ++ )
    {
        if(!color[i])
        {
            int ans = bfs(i);
            if(ans == -1)
            {
                 cout << "Impossible" << endl;
                 return;
            }
            sum += ans;
        }
    }

    cout << sum << endl;
}

int main()
{
   ios;
   int T = 1; while(T -- ) solve();
    return 0;
}

P1559 运动员最佳匹配问题: 经典KM算法 (暴搜被hack)

#include <bits/stdc++.h>
//#define int long long 
#define ios ios::sync_with_stdio(0),cin.tie(0)
#define fi first
#define se second
#define pb push_back
#define PII pair<int,int>
using namespace std;

const int N = 30,M = N * 2,INF = 0x3f3f3f3f;
int p[N][N],q[N][N];
bool stx[N],sty[N];
int n; 
int lx[N],ly[N];
int pi[N];
int minz;

bool find(int x)
{
    stx[x] = true;
    
    for(int i = 1;i <= n;i ++ ) // 遍历所有y点
    {
        if(!sty[i])
        {
            int t = lx[x] + ly[i] - (p[x][i] * q[i][x]);
            if(t == 0) // 代表这个y点有可能可以匹配
            {
                sty[i] = true;
                if(pi[i] == 0 || find(pi[i]))
                {
                    pi[i] = x;
                    return true;
                }
            }
            else if(t > 0)// 代表这个y点不可能匹配的,就取一个最小值
            {
                minz = min(minz,t);
            }
        }
    }
    
    return false;
}

void KM()
{
    for(int i = 1;i <= n;i ++ )
    {
        while(1) // 一直到整体可以下来没问题的时候再说
        {
            minz = INF;
            memset(stx, false, sizeof stx);
            memset(sty, false, sizeof sty);
            
            if(find(i)) break;
            
            for(int j = 1;j <= n;j ++ )
            if(stx[j])
            lx[j] -= minz;
            for(int j = 1;j <= n;j ++ )
            if(sty[j])
            ly[j] += minz;
        }
    }
}

void solve()
{
    cin >> n;
    for(int i = 1;i <= n;i ++ )for(int j = 1;j <= n;j ++ )cin >> p[i][j];
    for(int i = 1;i <= n;i ++ )for(int j = 1;j <= n;j ++ )cin >> q[i][j];
    {
    }
    
    for(int i = 1;i <= n;i ++ )
    {
        for(int j = 1;j <= n;j ++ )
        lx[i] = max(lx[i],p[i][j] * q[j][i]); // 初始化二分图左边的图
    }
    

    KM();
    int ans = 0;
    
    for(int i = 1;i <= n;i ++ )
    ans += p[pi[i]][i] * q[i][pi[i]];
    
    cout << ans << endl;
    
}

int main()
{
   ios;
   int T = 1; while(T -- ) solve();
    return 0;
}
  • P2055 [ZJOI2009] 假期的宿舍 :匈牙利算法题目比较绕口 考验阅读理解
  • 代码:
#include <bits/stdc++.h>
//#define int long long 
#define ios ios::sync_with_stdio(0),cin.tie(0)
#define fi first
#define se second
#define pb push_back
#define PII pair<int,int>
using namespace std;

const int N = 55,M = 4e4 + 100,INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], idx;
map<string,int> mp;
int n,m; 
bool st[N];
int match[N];
int stu[N],house[N],g[N][N];

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int find(int u)
{
    for(int i = 1;i <= n;i ++ )
    {
        if(stu[i] && g[u][i] && !st[i])
        // 这个人得是学生才有床铺这个人还得跟i这个人认识
        {
            st[i] = true;
            if(match[i] == 0 || find(match[i]))
            {
                match[i] = u;
                return true;
            }
        }
    }
    
    return false;
}

void solve()
{
    cin >> n;//现在有n个人
    memset(match,0,sizeof match);
    memset(g,0,sizeof g);
    
    for(int i = 1;i <= n;i ++ ) // 判断一下这个人是不是在校学生
    cin >> stu[i];
    for(int i = 1;i <= n;i ++ ) // 判断这个人有没有回家
    {
        cin >> house[i];
        if(stu[i] == 0) //不是在校学生,统一让他不回家
        house[i] = 0;
    }
    
    
    for(int i = 1;i <= n;i ++ ) // 如果第i个人个第j个人认识,那就是1,否则是0
        for(int j = 1;j <= n;j ++ ) 
        {
            cin >> g[i][j];
            if(i == j && stu[i]) // 这个人跟自己一定认识,也就是这个人可以睡到自己床铺上面
            {
                g[i][j] = 1;
            }
        }
            
            
    for(int i = 1;i <= n;i ++ ) // 枚举每一个没有回家的人,
    {
        if(!house[i]) // 代表这个人是不回家的人
        {
            memset(st,false,sizeof st);
            if(!find(i))
            {
                cout << "T_T" << endl;
                return;
            }
        }
    }
    
    cout << "^_^" << endl;
   
}

int main()
{
  ios;int T; cin >> T;while(T -- ) solve();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值