kuangbin带你飞专题十: 匹配问题 题解ing

版权声明:转载请说明,欢迎交流! https://blog.csdn.net/qq_39599067/article/details/82716233

A - Fire Net HDU - 1045

传送门

/*
Q版泡泡堂玩过吗?
给你n*n的图,x表示墙壁,问最多可以放多少个炸弹?
炸弹可炸此行此列的所有空位

分别按行按列缩点,求二分图最大匹配。
*/
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
#define all(x) x.begin(),x.end()
#define iis std::ios::sync_with_stdio(false)
#define mme(a,b) memset((a),(b),sizeof((a)))
using namespace std;
typedef long long LL;
typedef pair<LL,int> pii;
const int MXN = 1e2+7;
const int MXE = 4e6+1e5+7;
const int INF = 0x3f3f3f3f;
const int MOD = (int)1e9 + 7;

int n, m;
char ar[MXN][MXN];
int row[MXN][MXN], col[MXN][MXN];
int vis[MXN], is[MXN], be[MXN];
std::vector<int> son[MXN];
bool dfs(int u){
    for(auto x : son[u]){
        if(vis[x]) continue;
        vis[x] = 1;
        if(is[x] == -1 || dfs(is[x])){
            is[x] = u;
            be[u] = x;
            return true;
        }
    }
    return false;
}
int main(){
    while(~scanf("%d", &n)&&n){
        for(int i = 0; i <= 16; ++i)son[i].clear();
        for(int i = 0; i < n; ++i){
            scanf("%s", ar[i]);
        }
        mme(row, -1);
        mme(col, -1);
        int a = 0, b = 0;
        for(int i = 0, tmp; i < n; ++i){
            for(int j = 0; j < n; ++j){
                if(ar[i][j] == '.'&&row[i][j] == -1){
                    row[i][j] = ++a;
                    tmp = j;
                    while(tmp < n && ar[i][tmp] == '.'){
                        row[i][tmp] = a; tmp++;
                    }
                }
                if(ar[j][i] == '.'&&col[j][i] == -1){
                    col[j][i] = ++b;
                    tmp = j;
                    while(tmp < n && ar[tmp][i] == '.'){
                        col[tmp][i] = b; tmp++;
                    }
                }
            }
        }
        for(int i = 0; i < n; ++i){
            for(int j = 0; j < n; ++j){
                if(ar[i][j] == '.'){
                    son[row[i][j]].push_back(col[i][j]);
                }
            }
        }
        for(int i = 1 ; i <= a; ++i){
            sort(son[i].begin(),son[i].end());
            son[i].erase(unique(son[i].begin(),son[i].end()),son[i].end());
        }
        mme(is, -1);
        mme(be, -1);
        int cnt = 0;
        for(int i = 1; i <= a; ++i){
            if(be[i] != -1) continue;
            mme(vis, 0);
            if(dfs(i)){
                cnt++;
            }
        }
        printf("%d\n", cnt);
    }
    return 0;
}

B - The Accomodation of Students HDU - 2444

传送门

/*
判断是不是二分图,若是求最大匹配
判断二分图:
1.并查集
2.染色法
*/
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
#define all(x) x.begin(),x.end()
#define iis std::ios::sync_with_stdio(false)
#define mme(a,b) memset((a),(b),sizeof((a)))
using namespace std;
typedef long long LL;
typedef pair<LL,int> pii;
const int MXN = 1e3+7;
const int MXE = 4e6+1e5+7;
const int INF = 0x3f3f3f3f;
const int MOD = (int)1e9 + 7;

int n, m, FLAG;
char ar[MXN][MXN];
int vis[MXN], is[MXN], be[MXN];
std::vector<int> son[MXN];
int fa[MXN];
bool dfs(int u){
    for(auto x : son[u]){
        if(vis[x]) continue;
        vis[x] = 1;
        if(is[x] == -1 || dfs(is[x])){
            is[x] = u;
            be[u] = x;
            return true;
        }
    }
    return false;
}
int Fi(int x){
    return fa[x] == x? x: fa[x] = Fi(fa[x]);
}
void un(int a,int b){
    int pa = Fi(a), pb = Fi(b);
    if(pa != pb){
        fa[pb] = pa;
    }
}
int main(){
    while(~scanf("%d%d", &n, &m)){
        for(int i = 0; i <= n; ++i) son[i].clear();
        for(int i = 1; i <= n*2; ++i) fa[i] = i;
        for(int i = 0, u, v; i < m; ++i){
            scanf("%d%d", &u, &v);
            son[u].push_back(v);
            son[v].push_back(u);
            un(u, v + n);un(v, u + n);
        }
        FLAG = 0;
        int flag = 0;
        for(int i = 1; i <= n && flag == 0; ++i){
            if(Fi(i) == Fi(i+n)) flag = 1;
        }
        if(flag) {
            printf("No\n");
            continue;
        }
        mme(is, -1);
        mme(be, -1);
        int cnt = 0;
        for(int i = 1; i <= n; ++i){
            if(be[i] != -1) continue;
            mme(vis, 0);
            if(dfs(i)){
                cnt++;
            }
        }
        printf("%d\n", cnt/2);
    }
    return 0;
}

C - Courses HDU - 1083

传送门

/*
m门课程,n个学生,每个学生代表某些课程。
问是否存在此m个学生,满足每个学生能代表一个不同的课程,每门课程都有学生代表
即问是否存在一个m:m的完备匹配。
*/
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
#define all(x) x.begin(),x.end()
#define iis std::ios::sync_with_stdio(false)
#define mme(a,b) memset((a),(b),sizeof((a)))
using namespace std;
typedef long long LL;
typedef pair<LL,int> pii;
const int MXN = 1e3+7;
const int MXE = 4e6+1e5+7;
const int INF = 0x3f3f3f3f;
const int MOD = (int)1e9 + 7;

int n, m, FLAG;
char ar[MXN][MXN];
int vis[MXN], is[MXN], be[MXN];
std::vector<int> son[MXN];
int fa[MXN];
bool dfs(int u){
    for(auto x : son[u]){
        if(vis[x]) continue;
        vis[x] = 1;
        if(is[x] == -1 || dfs(is[x])){
            is[x] = u;
            be[u] = x;
            return true;
        }
    }
    return false;
}
int Fi(int x){
    return fa[x] == x? x: fa[x] = Fi(fa[x]);
}
void un(int a,int b){
    int pa = Fi(a), pb = Fi(b);
    if(pa != pb){
        fa[pb] = pa;
    }
}
int main(){
    int tim;
    scanf("%d", &tim);
    while(tim--){
        scanf("%d%d", &m, &n);
        for(int i = 0; i <= max(n,m); ++i) son[i].clear();
        for(int i = 0, u, v; i < m; ++i){
            scanf("%d", &u);
            while(u--){
                scanf("%d", &v);
                son[i+1].push_back(v);
            }
        }
        mme(is, -1);
        mme(be, -1);
        int cnt = 0;
        for(int i = 1; i <= m; ++i){
            if(be[i] != -1) continue;
            mme(vis, 0);
            if(dfs(i)){
                cnt++;
            }
        }
        if(cnt >= m) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

D - 棋盘游戏 HDU - 1281

传送门







KM算法模板(O(N^3))

一.顶点数较少的为X部,X部每个点顶标为该点关联的最大边的权值,Y部的顶点顶标为0。
二.对于X部中的每个顶点,在相等子图中用匈牙利算法找增广路径,如果没有找到,则修改顶标,扩大相等子图,继续找增广路径。当每个点都找到增广路径时,意味每个点都在匹配中,即找到了二分图的完备匹配。该完备匹配即为二分图的最佳匹配。

什么是相等子图呢?
 边权等于两端点的顶标之和的边,它们组成的图称为相等子图。

修改顶标:
目的:扩大相等子图。
 若未找到增广路径,一定找到了许多条从Xi出发并结束于X部的匹配边与未匹配边交替出现的路径,姑且称之为交错树。
 交错树中X部的顶点顶标减去一个值d,交错树中Y部的顶点顶标加上一个值d。

若它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图:
 1.两端都在交错树中的边(i,j),其顶标和没有变化;
 2.两端都不在交错树中的边(i,j),其顶标也没有变化;
 3.X端不在交错树中,Y端在交错树中的边(i,j),其顶标和增大;
它原来不属于相等子图,现在可能进入相等子图:
 4.X端在交错树中,Y端不在交错树中的边(i,j),其标和会减小。

三.当X部的所有顶点都找到了增广路径后,则找到了完备匹配,此完备匹配即为最佳匹配。

相等子图的若干性质

 在任意时刻,相等子图上的最大权匹配一定小于等于相等子图的顶标和。
 在任意时刻,相等子图的顶标和即为所有顶点的顶标和。
 扩充相等子图后,相等子图的顶标和将会减小。
 当相等子图的最大匹配为原图的完备匹配时,匹配边的权值和等于所有顶点的顶标和,此匹配即为最佳匹配。

bool dfs(int s){
    visx[s]=1;
    for(int i=1;i<=cnty;i++) 
        if(!visy[i]){
            int t=wx[s]+wy[i]-dis[s][i];
            if(t==0) {
                visy[i]=1;
                if(linky[i]==0||dfs(linky[i])){
                    linkx[s]=i,linky[i]=s;
                    return true;
                }
            }else if(t>0){//找出边权与顶标和的最小的差值
                if(t<minz)minz=t;
            }
        }
    return false;
}
void km(){
    memset(linkx,0,sizeof linkx);//linkx[i]表示与X部中点i匹配的点
    memset(linky,0,sizeof linky);
    for(int i=1;i<=cntx;i++){
        while(1){
            minz=INF;
            memset(visx,0,sizeof visx);
            memset(visy,0,sizeof visy);
            if(dfs(i))break;
            for(int j=1;j<=cntx;j++)//将交错树中X部的点的顶标减去minz
            if(visx[j])wx[j]-=minz;
            for(int j=1;j<=cnty;j++)//将交错树中Y部的点的顶标加上minz
            if(visy[j])wy[j]+=minz;
        }
    }
}

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 300 + 20;
const int inf = 0x3f3f3f3f;
int lx[maxn],ly[maxn];//结点顶标
int pre[maxn];//结点前驱
int slack[maxn];//结点 顶标与边权 最小差值
bool visx[maxn],visy[maxn];
int map[maxn][maxn];
int num;//村民 房子数量
//寻找可行边
bool dfs(int i){
    visx[i] = true;
    for(int j = 1;j <= num;j++){
        if(visy[j])continue;
        int t = lx[i] + ly[j] - map[i][j];
        if(t==0){//可行边
            slack[j] = 0;
            visy[j] = true;//放在下面应该也可以
            if(pre[j]==0 || dfs(pre[j])){
                pre[j] = i;
                return true;
            }
        }else slack[j] = min(slack[j],t);
    }
    return false;
}
int KM(){
    memset(pre, 0, sizeof(pre));
    memset(ly, 0, sizeof(ly));
    memset(lx, 0, sizeof(lx));
    for(int i = 1;i <= num;i++)
        for(int j = 1;j <= num;j++)
            if(map[i][j] > lx[i]) lx[i] = map[i][j];
    //对每一个x 结点求增广路径增加可行边
    for(int x = 1;x <= num;x++){
        for(int i = 1;i <= num;i++) slack[i] = inf;
        while(true){
            memset(visx, false, sizeof(visx));
            memset(visy, false, sizeof(visy));
            if(dfs(x)) break;
            //否则 求出常数 d ,顶标减d 
            int d = inf;
            for(int i = 1;i <= num;i++)
                if(!visy[i] && d > slack[i]) d = slack[i];
            for(int i = 1;i <= num;i++)
                if(visx[i]) lx[i] -= d;
            for(int i = 1;i <= num;i++)
                //如果i不在增广路径中 由于 visx[i] 已经减去 d 还要更新 slack值
                if(visy[i]) ly[i] += d;
                else slack[i] -= d;
        }
    }
    int ans = 0;
    for(int i = 1;i <= num;i++)
        if(pre[i] != 0) ans += map[pre[i]][i];
    return ans;
}
void solve(){
    cin>>num;
    for(int i = 1;i <= num;i++)
        for(int j = 1;j <= num;j++)
            cin>>map[i][j];
    cout<<KM()<<"\n";
}

const int N=310;
const int INF = 0x3f3f3f3f;
int match[N];
int lx[N],ly[N];
int sx[N],sy[N];
int weight[N][N];
int n;
int dfs(int x) {
    sx[x]=true;
    for(int i=0; i<n; i++) {
        if(!sy[i]&&lx[x]+ly[i]==weight[x][i]) {
            sy[i]=true;
            if(match[i]==-1||dfs(match[i])) {
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
int fax(int x) {
    if(!x) {
        for(int i=0; i<n; i++) {
            for(int j=0; j<n; j++) {
                weight[i][j]=-weight[i][j];
            }
        }
    }
    memset(match,-1,sizeof(match));
    for(int i=0; i<n; i++) {
        ly[i]=0;
        lx[i]=-INF;
        for(int j=0; j<n; j++) {
            if(weight[i][j]>lx[i]) {
                lx[i]=weight[i][j];
            }
        }
    }
    for(int i=0; i<n; i++) {
        while(1) {
            memset(sx,0,sizeof(sx));
            memset(sy,0,sizeof(sy));
            if(dfs(i))break;
/*将所有增广轨中的X方点的标号全部减去一个常数d,Y方点的标号全部加上一个常数d*/
            int mic=INF;
            for(int j=0; j<n; j++) {
                if(sx[j]) {
                    for(int k=0; k<n; k++) {
                        if(!sy[k]&&lx[j]+ly[k]-weight[j][k]<mic) {
                            mic=lx[j]+ly[k]-weight[j][k];
                        }
                    }
                }
            }
            if(mic==0) return -1;
            for(int j=0; j<n; j++) {
                if(sx[j]) lx[j]-=mic;
                if(sy[j]) ly[j]+=mic;
            }
        }
    }
    int sum=0;
    for(int i=0; i<n; i++) {
        if(match[i]>=0) {
            sum+=weight[match[i]][i];
        }
    }
    if(!x) sum=-sum;
    return sum;
}
void solve(){
  for(int i=0;i<n;++i){
    for(int j=0;j<n;++j){
      scanf("%d",&weight[i][j]);
    }
  }
  printf("%d\n",fax(1));
}

猜你喜欢

转载自blog.csdn.net/qq_39599067/article/details/82716233