二分图匹配

匈牙利

匈牙利模板

题目链接

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 1e5+10;

int mat[N];
bool vis[N], e[N][N];
int n, m, e_cnt;
bool dfs(int u) //dfs左部点 mat针对右部点
{
    for(int v = 1; v <= m; v ++) //枚举右部点是否可再和此时的左部点匹配
    {
        if(e[u][v] && !vis[v]) //如果这个右部点在此次匹配中还没有遍历过才去尝试它 
        {
            vis[v] = true;
            if(!mat[v] || dfs(mat[v])) //如果它已经匹配了1~(u-1)中的点,尝试为1~(u-1)的点重新匹配
            {
                mat[v] = u;
                return true;
            }
        }
    }
    return false;
}
int main(){
    scanf("%d%d%d", &n, &m, &e_cnt);
    for(int i = 1; i <= e_cnt; i ++)
    {
        int u, v; scanf("%d%d", &u, &v);
        e[u][v] = true;
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++) vis[j] = false;
        if(dfs(i)) ans ++;
    }
    printf("%d",ans);
    return 0;
}

372. 棋盘覆盖

题目链接
找到0元素:分成两个集合,每个集合内部无连边
找到1元素:每个节点只能和一条匹配边相连
行列和为偶数作为左部点,向其相邻的4个合法点(行列和必然为奇数,作为 右部点)连边,可以发现左部点和右部点内部无连边,且每个左部点只能向右部点连一条边。
note: 棋盘问题注意行,列,行列和的奇偶性,以奇偶性来确定左部点和右部点。

#include<bits/stdc++.h>
using namespace std;
const int N = 110, T = 110;
bool g[N][N];
int n, t;
inline int id(int x,  int y){return n * (x - 1) + y;}
int mat[N * N];
bool vis[N * N];
vector<int> e[N * N];
bool dfs(int u)
{
    for(auto v : e[u])
    {
        if(vis[v]) continue;
        vis[v] = true;
        if(!mat[v] || dfs(mat[v]))
        {
            mat[v] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d%d", &n, &t);
    while(t --)
    {
        int x, y; scanf("%d%d", &x, &y);
        g[x][y] = 1;
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            if((i + j) % 2 || g[i][j]) continue; //行列和为偶数作为左部点
            int l = id(i, j);
            if(!(i - 1 < 1 || g[i - 1][j])) e[l].push_back(id(i - 1, j));
            if(!(i + 1 > n || g[i + 1][j])) e[l].push_back(id(i + 1, j));
            if(!(j - 1 < 1 || g[i][j - 1])) e[l].push_back(id(i, j - 1));
            if(!(j + 1 > n || g[i][j + 1])) e[l].push_back(id(i, j + 1));
        }
    }
    int ans = 0;
    for(int i = 1; i <= n * n; i ++)
    {
        if(e[i].size())
        {
            memset(vis, 0, sizeof vis);
            if(dfs(i)) ans ++;
        }   
    }
    printf("%d", ans);
    return 0;
}

373. 車的放置

题目链接
将行作为左部点,列作为右部点,若该位置不禁止则从左部点向右部点连边。

374. 导弹防御塔【多重匹配拆点】

题目链接
题意: 求将敌人消灭需要的最短时间。

在二分图中需要:
找到0元素:分成两个集合,每个集合内部无连边
找到1元素:每个节点只能和一条匹配边相连

此题中一个塔可以炸多个敌人,此时不符合0元素,因此将每次轰炸(不同射出时间),即每次导弹作为一个左部点,相当于将塔进行拆点。然后二分消灭时间 m i d mid mid,若可在 m i d mid mid时间内从 u u u导弹炸到 v v v敌人,则从 u u u v v v连边。

note: 将塔拆点时,每个塔只需要最多拆 m m m个点,因为最多只需要炸 m m m个敌人。进行二分需要和多组测试样例一样进行初始化。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2510, M = 100;
int mat[M];
bool vis[M];
double t1;
int n, m, vol, t2;
struct Node
{
    double x, y, out;
}a[M], b[N];
vector<int> e[N];
double dis(int i, int j)
{
    return sqrt((a[i].x - b[j].x) * (a[i].x - b[j].x) + (a[i].y - b[j].y) * (a[i].y - b[j].y));
}
bool dfs(int u)
{
    for(auto v : e[u])
    {
        if(vis[v]) continue;
        vis[v] = true;
        if(!mat[v] || dfs(mat[v]))
        {
            mat[v] = u;
            return true;
        }
    }
    return false;
}
bool check(double mid)
{
    int ans = 0;
    memset(mat, 0, sizeof mat);
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 0; j < m; j ++)
        {
            int id = i + j * n;
            e[id].clear();
            for(int k = 1; k <= m; k ++)
                if(dis(k, id) <= vol * (mid - b[id].out)) e[id].push_back(k);
        }
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 0; j < m; j ++)
        {
            int id = i + j * n;
            if(!e[id].size()) continue;
            memset(vis, 0, sizeof vis);
            if(dfs(id)) ans ++;
        }
    }
    return ans == m;
}
int main()
{
    scanf("%d%d%lf%d%d", &n, &m, &t1, &t2, &vol);
    t1 /= 60;
    for(int i = 1; i <= m; i ++)
    {
        scanf("%lf%lf", &a[i].x, &a[i].y);
    }
    for(int i = 1; i <= n; i ++)
    {
        scanf("%lf%lf", &b[i].x, &b[i].y);
        b[i].out = t1;
        for(int j = 1; j < m; j ++)
        {
            b[i + j * n] = b[i];
            b[i + j * n].out = t1 + j * (t1 + t2);
        }
    }
    double l = 0, r = 1e6, mid;
    while(r - l > 1e-9)
    {
        mid = (l + r) / 2;
        if(check(mid)) r = mid;
        else l = mid;
    }
    printf("%.6f", l);
    return 0;
}

P1525 [NOIP2010 提高组] 关押罪犯【二分+染色法】

题目链接
存在二分图=不存在奇环=染色时连边两端颜色不同

#include<bits/stdc++.h>
using namespace std;
const int N = 20000 + 10, M = 200000 + 10;
struct E{
    int to, nxt, w;
}e[M];
int head[N], cnt;
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
}
int color[N];
int n, m;
bool check(int u, int col, int mid)
{
    color[u] = col;
    for(int i = head[u]; i; i = e[i].nxt)  
    {
        int v = e[i].to;
        if(e[i].w <= mid || color[v] == 3 - col) continue;
        if(color[v] == col || !check(v, 3 - col, mid)) 
            return false;
    }
    return true;
}
int main()
{
    scanf("%d%d", &n, &m);
    int r = 0;
    for(int i = 1; i <= m; i ++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w), add(v, u, w);
        r = max(r, w);
    }
    int l = 0, mid;
    while(l < r) 
    {
        mid = (l + r) / 2;
        int fl = true;
        for(int i = 1; i <= n; i ++) color[i] = 0;
        for(int i = 1; i <= n; i ++)
        {
            if(color[i]) continue;
            if(!check(i, 1, mid))
            {
                fl = false;
                break;
            }
        }
        if(fl) r = mid;
        else l = mid + 1;
    }
    printf("%d", l);
    return 0;
}

最大权完美匹配

Ants UVA - 1411

题目链接

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 105;
double w[N][N];
double la[N], lb[N], upd[N];
bool va[N], vb[N];
int mat[N], last[N];
int n;
bool dfs(int x, int fa)
{
    va[x] = true;
    for(int y = 1; y <= n; y ++)
        if(!vb[y])
        {
            if(la[x] + lb[y] - w[x][y] == 0)
            {
                vb[y] = true, last[y] = fa;
                if(!mat[y] || dfs(mat[y], y))
                {
                    mat[y] = x;
                    return true;
                }
            }
            else if(upd[y] > la[x] + lb[y] - w[x][y])
            {
                upd[y] = la[x] + lb[y] - w[x][y];
                last[y] = fa;
            }
        }
    return false;
}
double KM()
{
    for(int i = 1; i <= n; i ++)
    {
        la[i] = -1e18; lb[i] = 0, mat[i] = 0;
        for(int j = 1; j <= n; j ++)
            la[i] = max(la[i], w[i][j]);
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++) 
            upd[j] = 1e18, va[j] = vb[j] = last[j] = 0;
        int st = 0; mat[0] = i;
        while(mat[st])
        {
            double delta = 1e18;
            if(dfs(mat[st], st)) break;
            for(int j = 1; j <= n; j ++)
                if(!vb[j] && upd[j] < delta)
                {
                    delta = upd[j];
                    st = j;
                }
            for(int j = 1; j <= n; j ++)
            {
                if(va[j]) la[j] -= delta;
                if(vb[j]) lb[j] += delta;
                else upd[j] -= delta;
            }
            vb[st] = true;
        }
        while(st)
        {
            mat[st] = mat[last[st]];
            st = last[st];
        }
    }
    double ans = 0;
    for(int i = 1; i <= n; i ++) ans += w[mat[i]][i];
    return ans;
}
double x[N * 2], y[N * 2];
int main()
{
    int fl = false;
    while(~scanf("%d", &n))
    {
        if(!fl) fl = true;
        else puts("");
        for(int i = 1 + n; i <= 2 * n; i ++)
            scanf("%lf%lf", &x[i], &y[i]);
        for(int i = 1; i <= n; i ++)
            scanf("%lf%lf", &x[i], &y[i]);
        for(int i = 1; i <= n; i ++)
        {
            for(int j = 1; j <= n; j ++)
                w[i][j] = -sqrt(pow(x[i] - x[j + n], 2) + pow(y[i] - y[j + n], 2));
        }
        KM();
        for(int i = 1; i <= n; i ++) printf("%d\n", mat[i]);
    }
}

最小点覆盖

最少的点使得每条边都被覆盖,每条匹配边选择一个点,同时选择的那些点还覆盖了所有的非匹配边。点数等于最大匹配
因此可找出一个“2”要素:要不选择一种,要么选择另一种。
点集选择:
1.在找完最大匹配之后, 从左部每个失配点出发再执行dfs,并且对访问过的点进行标记
2. 选择左部未标记点和右部标记点则为最小点集

376. 机器任务

题目链接
note: 注意初始条件,模式为0即不需要切换模式。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 105;
bool vis[N];
int n, m, k, mat1[N];
bool g[N][N];
bool dfs(int u)
{
    for(int i = 1; i < m; i ++)
    {
        if(!g[u][i] || vis[i]) continue;
        vis[i] = true;
        if(mat1[i] == -1 || dfs(mat1[i]))
        {
            mat1[i] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    while(cin >> n >> m >> k)
    {
        memset(g, 0, sizeof g);
        while(k --)
        {
            int tmp, a, b; cin >> tmp >> a >> b;
            g[a][b] = true;
        }
        int ans = 0;
        memset(mat1, -1, sizeof mat1);
        for(int i = 1; i < n; i ++)
        {
            memset(vis, 0, sizeof vis);
            if(dfs(i)) ans ++;
        }
        cout << ans << endl;
    }

    return 0;
}

377. 泥泞的区域

题目链接
此题的“2”要素:一个泥泞的位置,要么被横放的木板覆盖,要么被竖放的木板覆盖。因此每个位置可以看成一条边,连接着两个点:横放木板和竖放木板的编号,然后对木板编号进行二分匹配找出最小点覆盖。最小点数表示的是可以覆盖每个泥泞块(每条边)的木板的数量。
note: 可在边缘加上干净地板方便编号统计。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 110, M = 11000;
int n, m;
char g[N][N];
int row_id[N][N], col_id[N][N], row_cnt, col_cnt;
vector<int> e[M];
int mat[M];
bool vis[M];
bool dfs(int u)
{
    for(auto it : e[u])
    {
        if(vis[it]) continue;
        vis[it] = true;
        if(!mat[it] || dfs(mat[it]))
        {
            mat[it] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) scanf("%s", g[i] + 1), g[i][m + 1] = '.';
    for(int i = 1; i <= m; i ++) g[n + 1][i] = '.';
    for(int i = 1; i <= n; i ++)
    {
        int start = -1;
        for(int j = 1; j <= m + 1; j ++)
        {
            if(g[i][j] == '.')//干净
            {
                if(start != -1)
                {
                    ++ row_cnt;
                    for(int k = start; k < j; k ++) row_id[i][k] = row_cnt;
                    start = -1;
                }
            }
            else if(start == -1) start = j;
        }
    }

    for(int j = 1; j <= m; j ++)
    {
        int start = -1;
        for(int i = 1; i <= n + 1; i ++)
        {
            if(g[i][j] == '.')
            {
                if(start != -1)
                {
                    ++ col_cnt;
                    for(int k = start; k < i; k ++) col_id[k][j] = col_cnt;
                    start = -1;
                }
            }
            else if(start == -1) start = i;
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
            if(g[i][j] == '.') continue;
            e[row_id[i][j]].push_back(col_id[i][j]);
        }
    }
    for(int i = 1; i <= row_cnt; i ++)
    {
        memset(vis, 0, sizeof vis);
        if(dfs(i)) ans ++;
    }
    printf("%d", ans);
    return 0;
}

最大独立集

选择最多的点,使得所有的点都没有公共边。将最小点集删除之后,剩下的点都无法连接。点数为原点数-最小点覆盖。

378. 骑士放置

题目链接
日型连边,每条边都是一端行列和为偶数,另一端行列和为奇数。为了让骑士互相不攻击,每条边的点只能选择一个点,也就是所有选择出来的点都没有公共边,用最大独立集。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 110, M = 1e5;
int n, m, t;
int mat[M];
bool vis[M], g[N][N];
inline int id(int x, int y){return (x - 1) * m + y;}
int dx[8] = {1, 1, -1, -1, 2, 2, -2, -2};
int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1};
vector<int> e[M];
bool dfs(int u)
{
    for(auto i : e[u])
    {
        if(vis[i]) continue;
        vis[i] = true;
        if(!mat[i] || dfs(mat[i]))
        {
            mat[i] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d%d%d", &n, &m, &t);
    for(int i = 1; i <= t; i ++)
    {
        int u, v; scanf("%d%d", &u, &v);
        g[u][v] = 1;
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
            if(g[i][j] || (i + j) % 2) continue;
            int l = id(i, j);
            for(int k = 0; k < 8; k ++)
            {
                int x = i + dx[k], y = j + dy[k];
                if(x < 1 || y < 1 || x > n || y > m || g[x][y]) continue;
                e[l].push_back(id(x, y));
            }
        }
    }

    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        {
            if (g[i][j] || (i + j) % 2) continue;
            
            memset(vis, 0, sizeof vis);
            if(dfs(id(i, j))) ans ++;            
            
        }
    printf("%d", n * m - t - ans);
    return 0;
}

最小路径覆盖

在节点数为 n n n的有向无环图中找到最少的不相交的路径,使得所有节点都被覆盖一次。
因为路径不相交,每个点的出度和入度最多为1,又因为节点都要被覆盖,因此每个点至少有一个度(出度或入度)。可以用二分图处理。建一个新图:将点拆成 2 n 2n 2n个,原图中每条边 ( u , v ) (u,v) (u,v)改成新图中 ( u , v + n ) (u, v+n) (u,v+n)的边。这样新图会变成一个二分图,因为满足“01”要素:
0要素:只有左边的点会向右边的点连边,两边内部的点不会相连:连的边显然满足 1 < = u < = n ,   1 + n < = v + 1 < = 2 ∗ n 1<=u<=n,\space1+n<=v+1<=2*n 1<=u<=n, 1+n<=v+1<=2n
1要素:左右每个点最多连一条边:二分图中匹配边 ( u , v ) (u,v) (u,v)表示 u u u有出度, v v v有入度,而因为路径不能相交,每个点的出度和入度均不可超过1,因此每个点只能被匹配一次。
最后左部点会剩下一些没有匹配的点,它们的出度为0,是每条路径的终点,为了使得路径更少,那么非匹配点会更少,因此求出最大匹配。最小路径覆盖= n n n-最大匹配

379. 捉迷藏

题目链接

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 210;
int n, m;
bool e[N][N], vis[N];
int mat[N];
bool dfs(int u)
{
    for(int i = 1; i <= n; i ++)
    {
        if(!e[u][i] || vis[i]) continue;
        vis[i] = true;
        if(!mat[i] || dfs(mat[i]))
        {
            mat[i] = u;

            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i ++){
        int u, v; scanf("%d%d", &u, &v);
        e[u][v] = true;
    }
    for(int k = 1; k <= n; k ++)
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                e[i][j] |= e[i][k] && e[k][j];
    int ans = n;
    for(int i = 1; i <= n; i ++)
    {
        memset(vis, 0, sizeof vis);
        if(dfs(i))
        {
            ans --;
        }
    }
    printf("%d", ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值