二分图匹配入门例题

先上一些概念:

  1. “任意两条边都没有公共端点”的最大的边的集合称为二分图的最大匹配。
  2. 二分图的一组匹配S是最大匹配,当且仅当图中不存在S的增广路。
  3. 二分图匹配的要素有2,一是0要素,节点可以分成独立的两个集合,每个集合内部有0条边;1要素,每个节点只能与1条边匹配。
  4. 二分图最小点覆盖(选最少的点使得所有边都被覆盖)包含的点数==最大匹配包含的边数
  5. 二分图最大独立集(任意两点之间都没有边相连,包含的点数最多)==n-最大匹配数。

二分图最大匹配的板子,复杂度是(V*E)点数乘以边数:

const int maxn=409;
const int maxm=1e5+7;

struct Edge{
    int v,next;
}edge[maxm];

int head[maxn],top;
void init(){
    memset(head,-1,sizeof(head));
    top=0;
}

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

int match[maxn];
bool vis[maxn];

bool dfs(int u){
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){
            vis[v]=1;
            if(!match[v]||dfs(match[v])){
                match[v]=u;
                return 1;
            }
        }
    }
    return 0;
}
int main(){
    int res=0;//答案;
    for(int i=1;i<=n;++i){//一边的点;
        memset(vis,0,sizeof(vis));
        if(dfs(i)) ++res;
    }    

    return 0;
}

二分图匹配的模型有两个要素:

1、节点能分成独立的两个集合,每个集合内部有0条边。

2、每个节点只能与1条匹配边匹配。 

一、257. 关押罪犯

显然这个答案具有单调性,所以我们可以二分答案,然后比答案大的两个人显然不可以分到一起,所以我们建图,将两个人连边,然后跑一下无向图染色就行了,不用匈牙利。

#include
using namespace std;

const int maxm=1e5+7;
const int maxn=2e4+7;

struct Node{
    int u,v,w;
}node[maxm];

struct Edge{
    int v,next;
}edge[maxm<<1];

int head[maxn],top;
void init(){
    memset(head,-1,sizeof(head));
    top=0;
}

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

int n,m;
bool color[maxn],vis[maxn];

bool dfs(int u,bool f){
    color[u]=f;
    vis[u]=1;
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){
            bool flag=dfs(v,!f);
            if(!flag) return 0;
        }
        else if(color[v]==color[u]) return 0;
    }
    return 1;
}

bool check(int mid){
    init();
    for(int i=1;i<=m;++i)
        if(node[i].w>mid) add(node[i].u,node[i].v),add(node[i].v,node[i].u);
    memset(color,0,sizeof(color));
    memset(vis,0,sizeof(vis));
    bool f=1;
    for(int i=1;i<=n;++i)
        if(!vis[i]) f&=dfs(i,1);
    return f;
}

int main(){
    int u,v,w;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        node[i].u=u;
        node[i].v=v;
        node[i].w=w;
    }
    int l=0,r=1000000000,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",l);

    return 0;
}

 

二、372. 棋盘覆盖

一张牌可以横着覆盖也可以竖着覆盖,这两个被覆盖的点可以理解为两个不同的集合连了一条边。而且一个格子肯定不可能被两张牌覆盖,于是可以将矩阵压缩成一维,暴力连边,跑匈牙利。

#include
using namespace std;

const int maxm=1e5+7;
const int maxn=1e4+7;

struct Edge{
    int v,next;
}edge[maxm<<1];

int head[maxn],top;
void init(){
    memset(head,-1,sizeof(head));
    top=0;
}

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

int n,m;
int match[maxn];
bool vis[maxn];

bool dfs(int u){
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){
            vis[v]=1;
            if(match[v]==-1||dfs(match[v])){
                match[v]=u;
                return 1;
            }
        }
    }
    return 0;
}

int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};

bool hh[109][109];

bool check(int i,int j){
    if(i>=0&&i=0&&j

 

三、373. 車的放置

一个车只能覆盖一行一列,一个行列也只能放一个车,所以可以将行与列分为A,B集合,建图跑匈牙利即可。

#include
using namespace std;

const int maxn=409;

struct Edge{
    int v,next;
}edge[maxn*maxn*2];

int head[maxn],top;
void init(){
    memset(head,-1,sizeof(head));
    top=0;
}

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

int match[maxn];
bool vis[maxn];
bool hh[maxn][maxn];

bool dfs(int u){
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){
            vis[v]=1;
            if(!match[v]||dfs(match[v])){
                match[v]=u;
                return 1;
            }
        }
    }
    return 0;
}



int main(){
    int u,v,w;
    init();
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    while(k--){
        scanf("%d%d",&u,&v);
        hh[u][v]=1;
    }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(!hh[i][j]) add(i,j+n),add(j+n,i);
    int res=0;
    for(int i=1;i<=n;++i){
        memset(vis,0,sizeof(vis));
        if(dfs(i)) ++res;
    }
    printf("%d\n",res);

    return 0;
}

四、374. 导弹防御塔

显然时间具有单调性,可以二分,然后就可以计算出来这个时间内能发射P颗导弹,一个塔可以在这个时间内射出若干颗导弹,那么可以将一个塔拆成P个不同的点,依次将m个入侵者看能不能与P个点连边,建图跑匈牙利判断即可。

#include
using namespace std;

const double epos=1.0e-7;
const int maxn=1e5+7;
int head[maxn],top;

void init(){
    top=0;
    memset(head,-1,sizeof(head));
}

struct Edge{
    int v,w,next;
}edge[maxn<<4];

void add(int u,int v,int w){
    edge[top].v=v;
    edge[top].w=w;
    edge[top].next=head[u];
    head[u]=top++;
}

int match[maxn];
bool vis[maxn];

bool dfs(int u){
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v]){
            vis[v]=1;
            if(!match[v]||dfs(match[v])){
                match[v]=u;
                return 1;
            }
        }
    }
    return 0;
}

double t1,t2,v;
int n,m;

pair ru[59],ta[59];

double juli(double x1,double y1,double x2,double y2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

double ju[59][59];

bool check(double mid){
    init();
    if(midmid) break;
                if(k>50) break;
                add(i,id,1);
                add(id,i,1);
                ++id;
                need+=t1+t2;
            }
        }
    memset(match,0,sizeof(match));
    int res=0;
    for(int i=1;i<=m;++i){
        memset(vis,0,sizeof(vis));
        if(!dfs(i)) return 0;
    }
    return 1;
}

int main(){
    scanf("%d%d%lf%lf%lf",&n,&m,&t1,&t2,&v);
    t1/=60;
    for(int i=1;i<=m;++i)
        scanf("%d%d",&ru[i].first,&ru[i].second);
    for(int i=1;i<=n;++i)
        scanf("%d%d",&ta[i].first,&ta[i].second);
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j) ju[i][j]=juli(ru[i].first,ru[i].second,ta[j].first,ta[j].second);
    double l=0,r=300000,mid;
    while(r-l>=epos){
        mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }
    printf("%.6f\n",r);

    return 0;
}

二分图带权匹配

KM算法只适用于满足完备匹配的图中才能求解,就是两个集合各有n个点,最终有n条匹配边。

五、poj 3565

二维平面上有2*n个点,n个白点,n个黑点,要求每个白点连接一个黑点,使得最后所有的线段都不相交,数据保证有解。

所有线段都不相交,就是使得一个白点连接与他最近的黑点。

那么将边权取成负数后,就转化为求带权最大匹配了。

#include
using namespace std;

const int maxn=109;

double W[maxn][maxn];
double la[maxn],lb[maxn];
bool va[maxn],vb[maxn];
int match[maxn];
int n;
double delta;
double upd[maxn];
const double epos=1.0e-8;

bool dfs(int u){
    va[u]=1;
    for(int v=1;v<=n;++v){
        if(!vb[v])
            if(fabs(la[u]+lb[v]-W[u][v])<=epos){
                vb[v]=1;
                if(!match[v]||dfs(match[v])){
                    match[v]=u;
                    return 1;
                }
            }
            else upd[v]=min(upd[v],la[u]+lb[v]-W[u][v]);
    }
    return 0;
}

double KM(){
    for(int i=1;i<=n;++i){
        la[i]=-1e10;
        lb[i]=0;
        for(int j=1;j<=n;++j)
            la[i]=max(la[i],W[i][j]);
    }
    for(int i=1;i<=n;++i)
        while(1){
            memset(va,0,sizeof(va));
            memset(vb,0,sizeof(vb));
            delta=1e10;
            for (int j = 1; j <= n; j++) upd[j] = 1e10;
            if(dfs(i)) break;
            for(int j=1;j<=n;++j)
                if(!vb[j]) delta=min(delta,upd[j]);
            for(int j=1;j<=n;++j){
                if(va[j]) la[j]-=delta;
                if(vb[j]) lb[j]+=delta;
            }
        }
    double ans=0;
    for(int i=1;i<=n;++i) ans+=W[match[i]][i];
    return ans;
}

pair A[maxn],B[maxn];

double juli(double x1,double y1,double x2,double y2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

int res[maxn];

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d%d",&A[i].first,&A[i].second);
    for(int i=1;i<=n;++i) scanf("%d%d",&B[i].first,&B[i].second);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            W[i][j]=-juli(A[i].first,A[i].second,B[j].first,B[j].second);
    KM();
    for(int i=1;i<=n;++i) res[match[i]]=i;
    for(int i=1;i<=n;++i) printf("%d\n",res[i]);


    return 0;
}

 

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