二分图匹配问题合集

定理1(Konig 定理):最小点覆盖数 = 最大匹配数

点覆盖:点集合使得任意一条边至少有一个端点在集合中。

定理2:最大独立集 = 顶点数 - 最大匹配数

独立集:点集合中任何两个顶点都不互相连接。

定理3:最小路径覆盖数 = 顶点数 – 最大匹配数

路径覆盖:任何一个点都属于且仅属于一条路径

HDU - 1045 - Fire Net

题目链接


题意:

给出一张图,‘X’代表墙,‘.’ 代表空地。在空地上放一些炮塔,炮塔不能处在同一行同一列,除非被墙隔开。问最多能放多少个炮塔。


题解:

这题其实可以二进制暴力模拟去做,但也可以用二分图来做。

考虑不存在墙的情况,那么可以把行和列当作两边的点,中间放炮塔当作边,进行二分图匹配即可。换句话说,就是把每一列缩成一点,每一行缩成一点。

如果存在墙,墙的两侧其实就相当于是两个互不相关的点。而对于一个联通串(即中间不存在墙),由于只能放一个炮台的特性,所以相当于一个点。

那么就首先从列出发,对每一列的联通串缩点并染色。然后从行出发同样的对联通串缩点,并与所染色的对应列联通串连边。最后进行二分图匹配即可。


#include
using namespace std;
const int N=1e3+7;
struct Edge{
    int u,v,w,nxt;
    Edge(int u=0,int v=0,int w=0,int nxt=0):u(u),v(v),w(w),nxt(nxt){}
}e[N*2];
int p[N],edn;
void add(int u,int v,int w){
    e[++edn]=Edge(u,v,w,p[u]);p[u]=edn;
    e[++edn]=Edge(v,u,w,p[v]);p[v]=edn;
}
int match[N],vis[N];
int n,m;
bool dfs(int u){
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(vis[v]) continue;
        vis[v]=1;
        if(match[v]==-1||dfs(match[v])){
            match[v]=u;
            return true;
        }
    }
    return false;
}
char mp[N][N],col[N][N];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF){
        if(n==0) break;
        memset(match,-1,sizeof(match));
        memset(p,-1,sizeof(p));edn=-1;
        for(int i=1;i<=n;i++)
            scanf("%s",mp[i]+1);
        int top=1;
        for(int j=1;j<=n;j++){
            for(int i=1;i<=n;i++){
                if(mp[i][j]=='X') top++;
                else col[i][j]=top;
            }
            top++;
        }
        top=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(mp[i][j]=='X') top++;
                else add(top,col[i][j]+100,1);
            }
            top++;
        }
        int ans=0;
        for(int i=1;i<=top;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

HDU - 2819 - Swap

题目链接


题意:

给出一张01矩阵,要求交换若干行若干列后使得主对角线全为1。并输出交换次数和过程。


题解:

根据线代的知识可以得知,只需考虑交换行就行。那么把行列当作点,1当作边,做一个二分图最大匹配。如果匹配书等于n那么就满足。

开一个a[i]数组记录现在第i行放的是初始的第几行。b[i]数组记录初始的第i行现在放在第几行。从头到尾扫一遍,逐一放进匹配行,记录一下交换顺序即可。


#include
using namespace std;
typedef long long ll;
const int N=1e3+7;
struct Edge{
    int v,nxt;
    Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*N*2];
int n,tmp;

int p[N],edn;
void add(int u,int v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
}
int vis[N],match[N];
bool dfs(int u){
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(vis[v]) continue;
        vis[v]=1;
        if(match[v]==-1||dfs(match[v])){
            match[v]=u;
            return true;
        }
    }
    return false;
}
int pr[N][2];
int a[N],b[N];
int main(){
    while(scanf("%d",&n)!=EOF){
        memset(p,-1,sizeof(p));edn=-1;
        memset(match,-1,sizeof(match));
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                scanf("%d",&tmp);
                if(tmp) add(i,j);
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        if(ans==n){
            int cnt=0;
            for(int i=1;i<=n;i++) a[i]=i,b[i]=i;
            for(int i=1;i<=n;i++){
                if(match[i]!=a[i]){
                    cnt++;
                    pr[cnt][0]=i;
                    pr[cnt][1]=b[match[i]];
                    swap(b[a[i]],b[match[i]]);
                    swap(a[i],a[pr[cnt][1]]);
                }
            }
            printf("%d\n",cnt);
            for(int i=1;i<=cnt;i++){
                printf("R %d %d\n",pr[i][0],pr[i][1]);
            }
        }
        else printf("-1\n");
    }
}

【HK算法模板】HDU - 2389 - Rain on your Parade

题目链接


题意:

二维平面上m个人和n把伞,一把伞只能给一个人。每个人都有自身的速度,问规定时间t内最多有几个人能拿到伞。

(1 <=t <= 5;1 <= m <= 3000;1 <= n <= 3000)


题解:

典型的二分图,人和伞作点,能在规定时间到的连一条边。

但这题的范围比较大。匈牙利算法的复杂度是O(VE),肯定会超时。

而Hopcroft-Carp适用于多点的情况,复杂度为O(sqrt(V)E),这样就能过了。


#include
using namespace std;
typedef long long ll;
const int N=3e3+7;
const int M=1e7+7;
const int inf=1<<28;
struct Edge{
    int v,nxt;
    Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[M];
int p[N],edn;
void add(int u,int v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
}
int mx[N],my[N],dx[N],dy[N],dis,n,m;
bool vis[N];
bool bfs(){
    queueq;
    dis=inf;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=0;idis)  break;
        for(int i=p[u];~i;i=e[i].nxt){
            int v=e[i].v;
            if(dy[v]==-1){
                dy[v]=dx[u]+1;
                if(my[v]==-1) dis=dy[v];
                else{
                    dx[my[v]]=dy[v]+1;
                    q.push(my[v]);
                }
            }
        }
    }
    return dis!=inf;
}
bool dfs(int u){
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(!vis[v]&&dy[v]==dx[u]+1){
            vis[v]=1;
            if(my[v]!=-1&&dy[v]==dis) continue;
            if(my[v]==-1||dfs(my[v])){
                my[v]=u;
                mx[u]=v;
                return 1;
            }
       }
    }
    return 0;
}
int hk(){
    int res=0;
    memset(mx,-1,sizeof(mx));
    memset(my,-1,sizeof(my));
    while(bfs()){
        memset(vis,0,sizeof(vis));
        for(int i=0;i

HDU - 4185 - Oil Skimming

题目链接


题意:

一张n*n的二维图,由‘#’和‘.’构成。要求用尽可能多的1*2的矩形去覆盖‘#’,矩形内不能含有‘.’,问最多能覆盖多少个矩形。

注意:这题的n没有题面说的那么大。


题解:

每一个‘#’就是一个点,相邻的‘#’互相连接。由于只与相邻的连接,所以肯定是二分图,跑一边就行了。


#include
using namespace std;
typedef long long ll;
const int N=1e3+7;
const int inf=1<<28;
struct Edge{
    int v,nxt;
    Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*4];
int p[N],edn;
void add(int u,int v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
}
char s[N][N];
int mp[N][N];
int vis[N],match[N];
bool dfs(int u){
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(vis[v]) continue;
        vis[v]=1;
        if(match[v]==-1||dfs(match[v])){
            match[v]=u;
            return true;
        }
    }
    return false;
}
int dir[4][2]={1,0,-1,0,0,1,0,-1};
int main(){
    int t,cs=0,n;
    scanf("%d",&t);
    while(t--){
        memset(p,-1,sizeof(p));edn=-1;
        memset(match,-1,sizeof(match));
        memset(mp,0,sizeof(mp));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%s",s[i]+1);
        int top=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(s[i][j]=='#') mp[i][j]=++top;
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(mp[i][j]){
                    for(int k=0;k<4;k++){
                        int di=i+dir[k][0];
                        int dj=j+dir[k][1];
                        if(mp[di][dj]){
                            add(mp[i][j],mp[di][dj]);
                        }
                    }
                }
            }
        }
        int ans=0;
        for(int i=1;i<=top;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("Case %d: %d\n",++cs,ans/2);
    }
}

【二分图染色判断奇环】Codeforces - 557D - Vitaly and Cycle

题目链接


题意:

在一张无向图中,最少添加多少边,能使得图包含奇环(存在一个环包含奇数个数的点)。并计算出有多少种添边的方案。


题解:

对于本题可以分情况讨论

1)如果图中本身就没有边,那么可以任选三个点连3条边。一共有\binom{n}{2}种方案。

2)如果图中本身就存在奇环,那么答案就是0 1。

关于奇环的判断可以利用黑白染色的进行搜索,如果一条边连了两个同色的那么就存在奇环。

如果不存在奇环,该图就是个二分图。而二分图也分了两种情况。

3)如果图中每个点都最多只连了一条边,也就是每个点的度数都小于等于1。那么只需选出一条边,另外再选一个点,连成三角形即可。所需边数是2,方案数是m*(n-2)。

4)对于每一个连通块进行黑白染色后,黑点与黑点连一条边,白点与白点连一条边也就能构造出奇环。所需添加的边数就是1,每一个连通块对答案的贡献是\binom{black}{2}+\binom{white}{2}


#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const ll N=1e6+7;
ll p[N],edn;
ll num[3],vis[N];
struct Edge{
    ll u,v,nxt;
    Edge(ll u=0,ll v=0,ll nxt=0):u(u),v(v),nxt(nxt){}
}e[N*2];
void add(ll u,ll v){
    e[++edn]=Edge(u,v,p[u]);p[u]=edn;
    e[++edn]=Edge(v,u,p[v]);p[v]=edn;
}
bool dfs(ll u){
    num[vis[u]]++;
    for(ll i=p[u];~i;i=e[i].nxt){
        ll v=e[i].v;
        if(vis[v]){
            if(vis[v]==vis[u]){
                return false;
            }
            continue;
        }
        vis[v]=3-vis[u];
        if(dfs(v)==false) return false;
    }
    return true;
}
int main()
{
    ll n,m,u,v;
    scanf("%lld%lld",&n,&m);
    if(m==0) printf("3 %lld\n",n*(n-1)*(n-2)/6);
    else{
        memset(p,-1,sizeof(p));edn=-1;
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&u,&v);
            add(u,v);
        }
        ll ans=0;
        memset(vis,0,sizeof(vis));
        bool flag=true;
        for(ll i=1;i<=n;i++){
            if(!vis[i]){
                num[1]=num[2]=0;
                vis[i]=1;
                flag=dfs(i);
                if(flag==false) break;
                ans=ans+num[1]*(num[1]-1)/2+num[2]*(num[2]-1)/2;
            }
        }
        if(flag){
            if(ans==0) printf("2 %lld\n",m*(n-2));
            else printf("1 %lld\n",ans);
        }
        else printf("0 1\n");
    }
}

POJ - 2594 - Treasure Exploration

题目链接


题意:

在一张有向无环图中,放置若干个机器人,每个机器人都有一个属于自己的路径。图中每一个节点都可以属于多个路径。问最少需要投放几个机器人才能遍历完图中每个节点。


题解:

粗一看以为是最小路径覆盖裸题,直接wa掉。

因为节点可以属于多个路径,所以不能裸着做,但只需另外floyd一下,把下一个节点能到的点直接连上就行了。

这题还是用矩阵好实现。


#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=5e2+7;
const int inf=1<<28;
int mp[N][N];
int vis[N],match[N];
int n,m,u,v;
bool dfs(int u){
    for(int v=1;v<=n;v++){
        if(mp[u][v]&&!vis[v]){
            vis[v]=1;
            if(match[v]==-1||dfs(match[v])){
                match[v]=u;
                return true;
            }
        }
    }
    return false;
}
int main(){

    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0) break;
        memset(match,-1,sizeof(match));
        memset(mp,0,sizeof(mp));
        for(int i=1;i<=m;i++){
            scanf("%d%d",&u,&v);
            mp[u][v]=1;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(!mp[i][j]) continue;
                for(int k=1;k<=n;k++){
                    if(!mp[j][k]) continue;
                    mp[i][k]=1; 
                }
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("%d\n",n-ans);
    }
}

HDU - 3829 -Cat VS Dog

题目链接


题意:

动物园有若干只狗和猫,每个小朋友都要么喜欢一只狗不喜欢一只猫,要么反过来。现在要移除一些狗和猫,小朋友只有在不移除自己喜欢的动物,移除了自己不喜欢的动物才会开心。问最多能让几个小朋友开心。


题解:

如果把猫和狗当作点怕是没法做的。

其实这题的矛盾在人,如果某个人喜欢的动物另一个人不喜欢,那么这两个人就只能满足一个。

所以可以把人当作节点,矛盾的人连边,做最大独立集即可。

还有没必要尝试读字符串里的数字,数字很有可能到超过个位。


#include
using namespace std;
typedef long long ll;
const int N=5e2+7;
struct Edge{
    int v,nxt;
    Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*N];
int n,tmp;
int p[N],edn;
void add(int u,int v){
    e[++edn]=Edge(v,p[u]);p[u]=edn;
    e[++edn]=Edge(u,p[v]);p[v]=edn;
}
int vis[N],match[N];
bool dfs(int u){
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(vis[v]) continue;
        vis[v]=1;
        if(match[v]==-1||dfs(match[v])){
            match[v]=u;
            return true;
        }
    }
    return false;
}
struct Node{
    char x[5],y[5];
}a[N];
char s1[5],s2[5];
int main(){
    int n,m,k;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        memset(p,-1,sizeof(p));edn=-1;
        memset(match,-1,sizeof(match));
        for(int i=1;i<=k;i++)
            scanf("%s%s",a[i].x,a[i].y);
        for(int i=1;i<=k;i++)
            for(int j=i+1;j<=k;j++)
                if(strcmp(a[j].x,a[i].y)==0||strcmp(a[j].y,a[i].x)==0)
                    add(i,j);
        int ans=0;
        for(int i=1;i<=k;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("%d\n",k-ans/2);
    }
}

 

你可能感兴趣的:(图论)