二分匹配

棋盘游戏 HDU - 1281 题目链接

行与列的二分匹配,暴力删除每一点求最新的二分匹配,比原先的小那就是重要点

#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 5;
struct node{
    int x,y;
}s[N * N];
int Map[N][N],vis[N],link[N];
int cnt[N];
bool dfs(int x,int m)
{
    for (int i = 1; i <= m; i++){
        if (!Map[x][i]) continue;
        if (vis[i]) continue;
        vis[i] = 1;
        if (!link[i] || dfs(link[i],m)){
            link[i] = x;
            return true;
        }
    }
    return false;
}
int main()
{
    int n,m,k,Case = 1;
    while(scanf("%d %d %d",&n,&m,&k) == 3){
        memset(Map,0,sizeof(Map));
        memset(cnt,0,sizeof(cnt));
        for (int i = 0; i < k; i++){
            scanf("%d %d",&s[i].x,&s[i].y);
            Map[s[i].x][s[i].y] = 1;
            cnt[s[i].x]++;
        }
        memset(link,0,sizeof(link));
        int mx = 0;
        for (int i = 1; i <= n; i++){
            memset(vis,0,sizeof(vis));
            if (cnt[i]){
                mx += dfs(i,m);
            }
        }
        int ans = 0;
        for (int i = 0; i < k; i++){
            Map[s[i].x][s[i].y] = 0;
            cnt[s[i].x]--;

            memset(link,0,sizeof(link));
            int tem = 0;
            for (int i = 1; i <= n; i++){
                memset(vis,0,sizeof(vis));
                if (cnt[i]){
                    tem += dfs(i,m);
                }
            }
            Map[s[i].x][s[i].y] = 1;
            cnt[s[i].x]++;

            if (tem < mx) ans++;
        }
        printf("Board %d have %d important blanks for %d chessmen.\n",Case++,ans,mx);
    }
    return 0;
}

Swap HDU - 2819 题目链接

题意

交换矩阵中的行或列,使矩阵的对角线都为1

思路

行列的二分图匹配
最后把每一列都换到对应位置即可

#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 5;
int a[N][N];
int link[N],vis[N];
vector<pair<int,int> >p;
bool dfs(int x,int n)
{
    for (int i = 1; i <= n; i++){
        if (!a[x][i]) continue;
        if (vis[i]) continue;
        vis[i] = 1;
        if (!link[i] || dfs(link[i],n)){
            link[i] = x;
            return true;
        }
    }
    return false;
}
int main()
{
    int n;
    while(scanf("%d",&n) == 1){
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                scanf("%d",&a[i][j]);
            }
        }
        memset(link,0,sizeof(link));
        int cnt = 0;
        for (int i = 1; i <= n; i++){
            memset(vis,0,sizeof(vis));
            cnt += dfs(i,n);
        }
        if (cnt < n) cout << -1 << endl;
        else {
            p.clear();
            for (int i = 1; i <= n; i++){
                int x = 1;
                while(link[x] != i) x++;
                if (x == i) continue;
                swap(link[x],link[i]);
                p.push_back({i,x});
            }
            cout << p.size() << endl;
            for (auto &k : p){
                cout << "C " << k.first << " " << k.second << endl;
            }
        }
    }
    return 0;
}

无题II HDU 2236 题目链接

题意

这是一个简单的游戏,在一个n*n的矩阵中,找n个数使得这n个数都在不同的行和列里并且要求这n个数中的最大值和最小值的差值最小。

思路

二分匹配差值

#include <bits/stdc++.h>
using namespace std;
const int N = 111;
const int INF = 0x3f3f3f3f;
int g[N][N];
int n;
int link[N],vis[N];
int mid,k;
bool dfs(int x)
{
    for (int i = 1; i <= n; i++){
        if (vis[i]) continue;
        if (g[x][i] < k || g[x][i] > k + mid) continue;
        vis[i] = 1;
        if (!link[i] || dfs(link[i])){
            link[i] = x;
            return true;
        }
    }
    return false;
}
bool solve()
{
    memset(link,0,sizeof(link));
    for (int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if (!dfs(i)) return false;
    }
    return true;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        int mx = -INF,mi = INF;
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                scanf("%d",&g[i][j]);
                mx = max(mx,g[i][j]);
                mi = min(mi,g[i][j]);
            }
        }
        int l = 0,r = mx - mi;
        int ans = r;
        while(l <= r){
            mid = (l + r) >> 1;
            bool flag = true;
            for (k = mi; k + mid <= mx; k++){
                if (solve()){
                    ans = mid;
                    r = mid - 1;
                    flag = false;
                    break;
                }
            }
            if (flag) l = mid + 1;
        }
        cout << ans << endl;
    }
    return 0;
}

Rain on your Parade HDU - 2389 题目链接

思路

匈牙利算法优化
Hopcroft-Karp算法模板题

#include <bits/stdc++.h>
using namespace std;
const int N = 3e3 + 5;
const int INF = 0x3f3f3f3f;
struct node{
    int x,y,v;
}s[N];
struct node2{
    int x,y;
}p[N];
int linku[N],linkv[N],vis[N];
int du[N],dv[N];
int dis;
vector<int> ve[N];
bool bfs(int n)
{
    memset(du,0,sizeof(du));
    memset(dv,0,sizeof(dv));
    queue<int> q;
    dis = INF;
    for (int i = 1; i <= n; i++){
        if (!linku[i]){
            q.push(i);
            du[i] = 0;
        }
    }
    while(!q.empty()){
        int u = q.front();
        q.pop();
        if (du[u] > dis) break;
        for (auto v : ve[u]){
            if (!dv[v]){
                dv[v] = du[u] + 1;
                if (!linkv[v]){
                    dis = dv[v];
                }
                else {
                    du[linkv[v]] = dv[v] + 1;
                    q.push(linkv[v]);
                }
            }
        }
    }
    return dis != INF;
}
bool dfs(int u)
{
    for (auto v : ve[u]){
        if (!vis[v] && dv[v] == du[u] + 1){
            vis[v] = 1;
            if (linkv[v] && dis == dv[v]) continue;
            if (!linkv[v] || dfs(linkv[v])){
                linku[u] = v;
                linkv[v] = u;
                return true;
            }
        }
    }
    return false;
}
int solve(int n)
{
    int ans = 0;
    memset(linku,0,sizeof(linku));
    memset(linkv,0,sizeof(linkv));
    while(bfs(n)){
        memset(vis,0,sizeof(vis));
        for (int i = 1; i <= n; i++){
            if (!linku[i] && dfs(i)) ans++;
        }
    }
    return ans;
}
int calc(int a,int b,int c,int d)
{
    return (a - b) * (a - b) + (c - d) * (c - d);
}
int main()
{
    int t,Case = 1;
    scanf("%d",&t);
    while(t--){
        int rain;
        scanf("%d",&rain);
        int n,m;
        scanf("%d",&n);
        for (int i = 1; i <= n; i++){
            scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].v);
            s[i].v *= rain;
            s[i].v *= s[i].v;
        }
        scanf("%d",&m);
        for (int i = 1; i <= m; i++){
            scanf("%d %d",&p[i].x,&p[i].y);
        }
        for (int i = 0; i < N; i++){
            ve[i].clear();
        }
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= m; j++){
                int tem = calc(s[i].x,p[j].x,s[i].y,p[j].y);
                if (tem <= s[i].v) ve[i].push_back(j);
            }
        }
        printf("Scenario #%d:\n%d\n\n",Case++,solve(n));
    }
    return 0;
}

Strategic Game HDU - 1054 题目链接

题意

有一个树,在一个节点上放士兵可以观察与其相连的所有点,问最少需要几个士兵就可以观察到全部节点

思路

(最小顶点覆盖数)

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 1555;
const int INF = 0x3f3f3f3f;
int vis[N];
int link[N];
vector<int> ve[N];
bool dfs(int u)
{
    for (int i = 0; i < (int)ve[u].size(); i++){
        int v = ve[u][i];
        if (vis[v]) continue;
        vis[v] = 1;
        if (link[v] == - 1 || dfs(link[v])){
            link[u] = v;
            link[v] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    int n;
    while(scanf("%d",&n) == 1){
        for (int i = 0; i <= n; i++){
            ve[i].clear();
        }
        for (int i = 0; i < n; i++){
            int u,m;
            scanf("%d:(%d)", &u, &m);
            for (int j = 0; j < m; j++){
                int v;
                scanf("%d",&v);
                ve[u].push_back(v);
                ve[v].push_back(u);
            }
        }
        int ans = 0;
        memset(link,-1,sizeof(link)); /// 节点0开始
        for (int i = 0; i < n; i++){
            memset(vis,0,sizeof(vis));
            if (link[i] == -1 && dfs(i)) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

Air Raid HDU - 1151 题目链接

题意

在一个城镇,有m个路口,和n条路,这些路都是单向的,而且路不会形成环,现在要弄一些伞兵去巡查这个城镇,伞兵只能沿着路的方向走,问最少需要多少伞兵才能把所有的路口搜一遍。
(点不能重复经过)

思路

有向图的最大匹配

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 155;
const int INF = 0x3f3f3f3f;
int vis[N];
int link[N];
vector<int> ve[N];
bool dfs(int u)
{
    for (int i = 0; i < (int)ve[u].size(); i++){
        int v = ve[u][i];
        if (vis[v]) continue;
        vis[v] = 1;
        if (!link[v] || dfs(link[v])){
            link[v] = u;
            return true;
        }
    }
    return false;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d %d",&n,&m);
        for (int i = 1; i <= n; i++){
            ve[i].clear();
        }
        for (int i = 0; i < m; i++){
            int u,v;
            scanf("%d %d",&u,&v);
            ve[u].push_back(v);
        }
        int ans = 0;
        memset(link,0,sizeof(link));
        for (int i = 1; i <= n; i++){
            memset(vis,0,sizeof(vis));
            if (dfs(i)) ans++;
        }
        printf("%d\n",n - ans);
    }
    return 0;
}

Treasure Exploration POJ - 2594 题目链接

题意

hdu1151 的改版,点可以重复经过

思路

使用floyd重新构图
最小路径覆盖 = 总点数 - 最大匹配数

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 555;
const int INF = 0x3f3f3f3f;
int vis[N];
int link[N];
int mp[N][N];
bool dfs(int u,int n)
{
    for (int v = 1; v <= n; v++){
        if (!mp[u][v] || v == u) continue;
        if (vis[v]) continue;
        vis[v] = 1;
        if (!link[v] || dfs(link[v],n)){
            link[v] = u;
            return true;
        }
    }
    return false;
}
void floyd(int n)
{
    for (int k = 1; k <= n; k++){
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                if (mp[i][k] && mp[k][j]){
                    mp[i][j] = 1;
                }
            }
        }
    }
}
int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m) == 2 && (n || m)){
        memset(mp,0,sizeof(mp));
        for (int i = 0; i < m; i++){
            int u,v;
            scanf("%d %d",&u,&v);
            mp[u][v] = 1;
        }
        floyd(n);
        int ans = 0;
        memset(link,0,sizeof(link));
        for (int i = 1; i <= n; i++){
            memset(vis,0,sizeof(vis));
            if (dfs(i,n)) ans++;
        }
        printf("%d\n",n - ans);
    }
    return 0;
}

Cat VS Dog HDU - 3829题目链接

题意

动物园有N只猫,M只狗,P个小孩。每个小孩都有自己喜欢的动物和讨厌的动物,如果他喜欢狗,那么就讨厌猫, 如果他讨厌狗,那么他就喜欢猫。某个小孩能开心,当且仅当他喜欢的动物留在动物园和讨厌的动物不在动物园里面。 现让管理员通过带走某些动物,问最多能使多少个孩子开心。

思路

最大独立集 = 总点数 - 最大匹配数

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 555;
const int INF = 0x3f3f3f3f;
struct node{
    char s1[5],s2[5];
}s[N];
int vis[N];
int link[N];
vector<int> ve[N];
bool dfs(int u)
{
    for (int i = 0; i < (int)ve[u].size(); i++){
        int v = ve[u][i];
        if (vis[v]) continue;
        vis[v] = 1;
        if (!link[v] || dfs(link[v])){
            link[v] = u;
            link[u] = v;
            return true;
        }
    }
    return false;
}
int main()
{
    int n,m,k;
    while(scanf("%d %d %d",&n,&m,&k) == 3){
        for (int i = 0; i < k; i++){
            scanf("%s %s",s[i].s1,s[i].s2);
        }
        for (int i = 1; i <= k; i++){
            ve[i].clear();
        }
        for (int i = 0; i < k; i++){
            for (int j = i + 1; j < k; j++){
                if (strcmp(s[i].s2,s[j].s1) == 0 || strcmp(s[i].s1,s[j].s2) == 0){
                    ve[i + 1].push_back(j + 1);
                    ve[j + 1].push_back(i + 1);
                }
            }
        }
        int ans = 0;
        memset(link,0,sizeof(link));
        for (int i = 1; i <= k; i++){
            memset(vis,0,sizeof(vis));
            if (!link[i] && dfs(i)) ans++;
        }
        printf("%d\n",k - ans);
    }
    return 0;
}

HDU 3081 题目链接

题意

n个男生,n个女生,接下来有 m个关系,u v表示第 u 个女生和第 v个男生可以配对,然后接下来有 f 个关系,u v表示第 u个女生和第v个女生是好友,如果 u 和 v可以配对,u 和 w是好友,那么w 和 v也是可以配对的.问知道这些人的关系,最多可以完全配对多少次(完全配对是指n个男生和n个女生都可以配对)?

思路

floyd建图,然后求完美匹配,匹配完成后就删边,直到不能匹配为止
(这道题是在网络流专题里的,使用二分和并查集最大流也可以解)

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 222;
const int INF = 0x3f3f3f3f;
int vis[N];
int pre[N];
int link[N];
int g[N][N];
int n,m,f;
bool dfs(int u)
{
    for (int v = 1; v <= n; v++){
        if (!g[u][v]) continue;
        if (vis[v]) continue;
        vis[v] = 1;
        if (!link[v] || dfs(link[v])){
            link[v] = u;
            return true;
        }
    }
    return false;
}
int Find(int x)
{
    return pre[x] == x ? x : pre[x] = Find(pre[x]);
}
void Union(int a,int b)
{
    int x = Find(a),y = Find(b);
    if (x != y){
        pre[x] = y;
    }
}
int solve()
{
    memset(link,0,sizeof(link));
    for (int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if (!dfs(i)) return false;
    }
    return true;
}
void build()
{
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){
            if (Find(i) == Find(j)){
                for (int k = 1; k <= n; k++){
                    if (g[i][k]) g[j][k] = 1;
                }
            }
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d %d %d",&n,&m,&f);
        memset(g,0,sizeof(g));
        for (int i = 0; i < m; i++){
            int u,v;
            scanf("%d %d",&u,&v);
            g[u][v] = 1;
        }
        for (int i = 1; i <= n; i++){
            pre[i] = i;
        }
        for (int i = 0; i < f; i++){
            int u,v;
            scanf("%d %d",&u,&v);
            Union(u,v);
        }
        build();
        int ans = 0;
        while(true){
            if (!solve()) break;
            ans++;
            for (int i = 1; i <= n; i++){
                g[link[i]][i] = 0;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值