[kuangbin带你飞]专题十 匹配问题

 
 
A-L 二分匹配 M-O 二分图多重匹配 P-Q 二分图最大权匹配 R-S 一般图匹配带花树 模板请自己找
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


61 / 72 Problem A HDU 1045 Fire Net

s.二分匹配或者暴搜

52 / 112 Problem B HDU 2444 The Accomodation of Students

d.有一些学生,他们之间有些互相认识,有些不认识,其中若A和B认识,B和C认识,并不代表A和C认识,现在提供互相认识的学生编号,问是否能将这些学生分成两组,同一组的学生互相不认识,如果可以,则分配给这些学生双人间,只有是朋友才能住双人间,问最多要准备几间房

即判断是不是二分图,若是,其最大匹配数是多少?

s.首先用交叉染色法判断是不是二分图,然后用匈牙利算法求最大匹配。

c.唯一比较坑的是边数开了1w,提交G++超时!而用C++却Runtime Error,才知道可能是数组原因。。

#include<iostream>
#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;

#define MAXN 205//点数
#define MAXM 41000//边数。不要吝啬内存,只要不超限。一定要开够

int color[MAXN];
int n;

struct Edge{
    int to,next;
}edge[MAXM];

int head[MAXN];
int tot;

void addedge(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}

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

bool bfs(int start){//bfs交叉染色,两种颜色标记为 1 和 -1,未染色标记为 0
    int v;
    queue<int>q;
    color[start]=1;
    q.push(start);

    while(!q.empty()){
        int temp=q.front();
        q.pop();

        for(int i=head[temp];i!=-1;i=edge[i].next){//

            v=edge[i].to;

            if(color[v]==0){//未染色
                if(color[temp]==1){
                    color[v]=-1;
                }
                else{
                    color[v]=1;
                }
                q.push(v);
            }
            else{//已染色
                if(color[v]==color[temp]){//相邻的两点颜色相同
                    return false;//不能交叉染色
                }
            }

        }
    }
    return true;
}

//匈牙利算法
int linker[MAXN];
bool used[MAXN];

bool dfs(int u){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(!used[v]){
            used[v]=true;
            if(linker[v]==-1||dfs(linker[v])){
                linker[v]=u;
                return true;
            }
        }
    }
    return false;
}
int hungary(){
    int res=0;
    memset(linker,-1,sizeof(linker));
    //点的编号0~uN-1
    for(int u=0;u<n;u++){
        if(color[u]==-1)continue;//只看颜色是1的集合

        memset(used,false,sizeof(used));
        if(dfs(u))res++;
    }
    return res;
}

int main(){

    int m;
    int u,v;
    bool flag;

    while(~scanf("%d",&n)){

        memset(color,0,sizeof(color));
        init();

        scanf("%d",&m);

        for(int i=0;i<m;++i){
            scanf("%d%d",&u,&v);

            addedge(u-1,v-1);
            addedge(v-1,u-1);
        }

        flag=true;

        for(int i=0;i<n;++i){
            if(color[i]!=0)continue;//已经染色
            if(!bfs(i)){//不是二分图
                flag=false;
                break;
            }
        }

        if(flag){
            printf("%d
",hungary());
        }
        else{
            printf("No
");
        }
    }

    return 0;
}
View Code

c2.这里的程序hungary()用vector实现,同时染色用的dfs

/*
HDU 2444 The Accomodation of Students

*/
#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
#define MAXN 202
vector<int>EV[MAXN];
int linker[MAXN];
bool used[MAXN];
int uN;
int matchs[MAXN],cnt[MAXN];
bool dfs(int u)
{
    int i;
    for(i=0;i<EV[u].size();i++)
    {
        int v=EV[u][i];
        if(!used[v])
        {
            used[v]=true;
            if(linker[v]==-1||dfs(linker[v]))
            {
                linker[v]=u;
                return true;
            }    
        }    
    }
    return false;    
}   
int hungary()
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=1;u<=uN;u++)
    {
        memset(used,false,sizeof(used));
        if(dfs(u))  res++;
    }
    return res;    
}  
bool judge(int x,int y)
{
    int i;
    for(i=0;i<EV[x].size();i++)
    {
        if(cnt[EV[x][i]]==0)
        {
            cnt[EV[x][i]]=-1*y;
            matchs[EV[x][i]]=true;
            if(!judge(EV[x][i],-1*y)) return false;
        }   
        else if(cnt[EV[x][i]]==y)  return false; 
    }    
    return true;
}    
bool matched()
{
    int i;
    memset(matchs,false,sizeof(matchs));
    for(i=1;i<=uN;i++)
    {
        if(EV[i].size()&&!matchs[i])
        {
            memset(cnt,0,sizeof(cnt));
            cnt[i]=-1;
            matchs[i]=true;
            if(!judge(i,-1)) return false;
        }    
    }
    return true;    
}     
int main()
{
    int m;
    int i;
    int u,v;
    while(scanf("%d%d",&uN,&m)!=EOF)
    {
        for(i=1;i<=uN;i++)
          if(EV[i].size())  EV[i].clear();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            EV[u].push_back(v);
            EV[v].push_back(u);
        }    
     
         if(matched())
                  printf("%d
",hungary()/2);
         else  printf("No
");    
    } 
    return 0;
}
View Code

45 / 86 Problem C HDU 1083 Courses

s.最大匹配


44 / 63 Problem D HDU 1281 棋盘游戏

s.二分匹配


35 / 85 Problem E HDU 2819 Swap

s.二分匹配,记录过程


29 / 110 Problem F HDU 2389 Rain on your Parade

s.先用匈牙利算法的DFS版本,一直超时,

后改用Hopcroft-Carp算法,顺利AC。


29 / 77 Problem G HDU 4185 Oil Skimming

s.二分匹配


27 / 39 Problem H POJ 3020 Antenna Placement

s.二分匹配,主要是建图


28 / 44 Problem I HDU 1054 Strategic Game

此题就是二分图的最小点覆盖。
点比较多,1500个。普通的匈牙利算法会超时的。可以用邻接表实现匈牙利算法。
但是我启用二分匹配中的战斗机:Hopcroft-Carp的算法。专门处理点比较多的情况。
 
好像也可以用树形DP做。等下来学习下。


25 / 36 Problem J HDU 1151 Air Raid

d.一个有向无环图(DAG),M个点,K条有向边,求DAG的最小路径覆盖数

s.DAG的最小路径覆盖数=DAG图中的点数-相应二分图中的最大匹配数

/*
顶点编号从0开始的
邻接矩阵(匈牙利算法)
二分图匹配(匈牙利算法的DFS实现)(邻接矩阵形式)
初始化:g[][]两边顶点的划分情况
建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
g没有边相连则初始化为0
uN是匹配左边的顶点数,vN是匹配右边的顶点数
左边是X集,右边是Y集
调用:res=hungary();输出最大匹配数
优点:适用于稠密图,DFS找增广路,实现简洁易于理解
时间复杂度:O(VE)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

const int MAXN=512;
int uN,vN;//u,v 的数目,使用前面必须赋值
int g[MAXN][MAXN];//邻接矩阵,记得初始化
int linker[MAXN];//linker[v]=u,表示v(右边Y集合中的点)连接到u(左边X集合中的点)
bool used[MAXN];
bool dfs(int u){//判断以X集合中的节点u为起点的增广路径是否存在
    for(int v=0;v<vN;v++)//枚举右边Y集合中的点
        if(g[u][v]&&!used[v]){//搜索Y集合中所有与u相连的未访问点v
            used[v]=true;//访问节点v
            if(linker[v]==-1||dfs(linker[v])){//是否存在增广路径
                //若v是未盖点(linker[v]==-1表示没有与v相连的点,即v是未盖点),找到增广路径
                //或者存在从与v相连的匹配点linker[v]出发的增广路径
                linker[v]=u;//设定(u,v)为匹配边,v连接到u
                return true;//返回找到增广路径
            }
        }
        return false;
}
int hungary(){//返回最大匹配数(即最多的匹配边的条数)
    int res=0;//最大匹配数
    memset(linker,-1,sizeof(linker));//匹配边集初始化为空
    for(int u=0;u<uN;u++){//找X集合中的点的增广路
        memset(used,false,sizeof(used));//设Y集合中的所有节点的未访问标志
        if(dfs(u))res++;//找到增广路,匹配数(即匹配边的条数)+1
    }
    return res;
}

int main(){
    int i,ans;
    int K,M;
    int u,v;
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&M,&K);
        uN=M;//匹配左边的顶点数
        vN=M;//匹配右边的顶点数
        memset(g,0,sizeof(g));//二分图的邻接矩阵初始化
        for(i=0;i<K;++i){
            scanf("%d%d",&u,&v);
            g[--u][--v]=1;//顶点编号从0开始的
        }
        ans=M-hungary();
        printf("%d
",ans);
    }
    return 0;
}
View Code

26 / 64 Problem K POJ 2594 Treasure Exploration

此题跟普通的路径覆盖有点不同。
You should notice that the roads of two different robots may contain some same point.
也就是不同的路径可以有重复点。
先用floyed求闭包,就是把间接相连的点也连起来。


26 / 61 Problem L HDU 3829 Cat VS Dog

最大独立集


23 / 56 Problem M POJ 2289 Jamie's Contact Groups

多重匹配+二分

貌似也可以用最大流+二分做

d.Jamie有很多联系人,但是很不方便管理,他想把这些联系人分成组,已知这些联系人可以被分到哪个组中去,而且要求每个组的联系人上限最小,即有一整数k,使每个组的联系人数都不大于k,问这个k最小是多少?

s.一对多的二分图的多重匹配。二分图的多重匹配算法的实现类似于匈牙利算法,对于集合x中的元素xi,找到一个与其相连的元素yi后,检查匈牙利算法的两个条件是否成立,若yi未被匹配,则将

xi,yi匹配。否则,如果与yi匹配的元素已经达到上限,那么在所有与yi匹配的元素中选择一个元素,检查是否能找到一条增广路径,如果能,则让出位置,让xi与yi匹配。

二分求出limit,知道找到可以构成多重匹配的最小限制limit,在main函数中二分搜索。

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
#define N 1010
int vis[N], maps[N][N], ans, n, m;
struct node
{
    int cnt;///和yi相匹配的个数;
    int k[N];///和yi相匹配的x的集合;
}Linky[N];

bool Find(int u, int limit)
{
    for(int i=1; i<=m; i++)
    {
        if(!vis[i] && maps[u][i])
        {
            vis[i] = 1;
            if(Linky[i].cnt < limit)
            {
                Linky[i].k[ Linky[i].cnt++ ] = u;
                return true;
            }
            for(int j=0; j<Linky[i].cnt; j++)
            {
                if(Find( Linky[i].k[j], limit ))
                {
                    Linky[i].k[j] = u;
                    return true;
                }
            }
        }
    }
    return false;
}

bool xyl(int limit)///匈牙利算法;
{
    memset(Linky, 0, sizeof(Linky));
    for(int i=1; i<=n; i++)
    {
        memset(vis, 0, sizeof(vis));
        if(!Find(i, limit))///当前的limit让i没有匹配,所以不能用limit;
            return false;
    }
    return true;
}

int main()
{
    int x;
    char s[20], ch;
    while(scanf("%d %d", &n, &m), m+n)
    {
        memset(maps, 0, sizeof(maps));
        for(int i=1; i<=n; i++)
        {
            scanf("%s", s);
            while(1)
            {
                scanf("%d%c", &x, &ch);
                maps[i][x+1] = 1;
                if(ch == '
')
                    break;
            }
        }
        int L = 1, R = n;
        ans = n;
        while(L <= R)
        {
            int mid = (L+R)/2;
            if(xyl(mid))///如果当前mid满足题意;
            {
                R = mid-1;
                ans = mid;
            }
            else
                L = mid+1;
        }
        printf("%d
", ans);
    }
    return 0;
}
View Code

15 / 74 Problem N POJ 2112 Optimal Milking

floyd+二分+最大流

题目描述:
农场主John将他的K(1≤K≤30)个挤奶器运到牧场,在那里有C(1≤C≤200)头奶牛,在奶
牛和挤奶器之间有一组不同长度的路。K个挤奶器的位置用1~K的编号标明,奶牛的位置用K+1~
K+C的编号标明。
每台挤奶器每天最多能为M(1≤M≤15)头奶牛挤奶。
编写程序,寻找一个方案,安排每头奶牛到某个挤奶器挤奶,并使得C头奶牛需要走的所有
路程中的最大路程最小。每个测试数据中至少有一个安排方案。每条奶牛到挤奶器有多条路。
// 本题的求解算法:先用Floyd算法求出能达到的任意两点之间的最短路径,然后用Dinic算法
// 求最大流;搜索最大距离的最小值采用二分法进行。

// 建图问题 : 给个源点 s 。源点到奶牛的流量为1 如果奶牛到达挤奶器距离在max_min范围内 那没就然 该奶牛到该挤奶器的流量为1
// 再给个汇点 那么挤奶器到汇点的流量就是M 正好符合限制
/// 然后二分查找 max_min就可以了

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <math.h>
#include <stdio.h>
#include <string.h>
using namespace std;
#define MOD 1000000007
#define INF 1000000000
#define maxn 310
#define maxm 48010
#define LL  __int64//long long
int M,K,C,N;
int cap[maxn][maxn],flow[maxn][maxn];
int dis[maxn][maxn];//,bl[1100];
void build(int max_min){  // 建图
    int i,j;
    memset(cap,0,sizeof(cap));
    memset(flow,0,sizeof(flow));
   for(i=1;i<=K;i++) cap[i][N+1]=M;
   for(i=K+1;i<=N;i++) cap[0][i]=1;
   for(i=K+1;i<=N;i++)
    for(j=1;j<=K;j++)
     if(dis[i][j]<=max_min) cap[i][j]=1;
}
int level[maxn];
bool BFS(int s,int t){
   memset(level,0,sizeof(level));
   queue<int> Q;
   int u;
   int i;
   Q.push(s);
   level[s]=1;
   while(!Q.empty()){
      u=Q.front();
      Q.pop();
      if(u==t) return true;
      for(i=1;i<=t;i++)
        if(!level[i]&&cap[u][i]>flow[u][i])
        {
            level[i]=level[u]+1;
            Q.push(i);
        }
   }
   return false;
}
int dfs(int u,int maxf,int t){
    if(u==t||maxf==0) return maxf;
    int ret=0,f,i;
    for(i=1;i<=t;i++)
      if(cap[u][i]>flow[u][i]&&level[u]+1==level[i]){
           f= dfs(i,min(maxf,cap[u][i]-flow[u][i]),t);
           flow[u][i]+=f;
           flow[i][u]-=f;
           maxf-=f;
           ret+=f;
           if(maxf==0) break;
    }
    return ret;
}
int Dinic(int s,int t){
   int flow=0;
   while(BFS(s,t)){
    flow+=dfs(s,INF,t);
   }
   return flow;
}
void init(){
   // memset(cap,0,sizeof(cap));
  //  memset(flow,0,sizeof(flow));
}
int main(){
    int i,j,k;
   while(scanf("%d %d %d",&K,&C,&M)!=EOF){

    N=K+C;
    for(i=1;i<=N;i++)
       for(j=1;j<=N;j++){
        scanf("%d",&dis[i][j]);
        if(dis[i][j]==0) dis[i][j]=INF;
    }
    for(k=1;k<=N;k++)
       for(i=1;i<=N;i++)
         for(j=1;j<=N;j++)
            if(dis[i][j]>dis[i][k]+dis[k][j])
              dis[i][j]=dis[i][k]+dis[k][j];
    /* for(i=0;i<=N+1;i++,printf("
"))
        for(k=0;k<=N+1;k++)
         printf("%d ",cap[i][k]);*/
      int L=0,R=100000,mid;
      int tp;
      while(L<R){// 二分枚举 max_min
         mid=(L+R)>>1;
         build(mid);
         tp=Dinic(0,N+1);
         if(tp>=C) R=mid;
         else L=mid+1;
      }
     printf("%d
",R);
   }

   return 0;
}
View Code

14 / 31 Problem O POJ 3189 Steady Cow Assignment

二分图多重匹配/网络流

题目描述:有n头奶牛,m个棚,每个奶牛对每个棚都有一个喜爱程度。当然啦,棚子也是有脾气的,并不是奶牛想住进来就住进来,

超出棚子的最大容量了,棚子是拒绝的。现在要给每个奶牛安家,本宝宝是一个公正无私的人类,

所以要找一个奶牛喜爱程度差值最小的方案(虽然这样大家的喜爱程度可能普遍偏低,因为绝对公平并不代表合理啊),问喜爱程度的区间最小为多大?

解题思路:每个棚子并不是住一个奶牛,所以是多重匹配咯。匹配的时候二分枚举喜爱程度的区间大小,根据区间大小来枚举区间的起点和终点,

然后跑多重匹配判断是否合法即可。

数据读入的时候maps[i][j]并不是i_th奶牛对j_th棚子的喜爱值,而是i_th奶牛对maps[i][j]棚子的喜爱程度为j。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1010;
int maps[maxn][22], vis[22], used[22][maxn];
int link[22], limit[22], n, m, s, e, mid;
bool Find (int u)
{
    for (int i=1; i<=m; i++)
    {//多重匹配
        if (!vis[i] && maps[u][i]<e && s<=maps[u][i])
        {
            vis[i] = 1;
            if (link[i] < limit[i])
            {//i棚子未满
                used[i][link[i] ++] = u;
                return true;
            }
            for (int j=0; j<limit[i]; j++)//i棚子已满,寻找增广路
                if (Find(used[i][j]))
                {
                    used[i][j] = u;
                    return true;
                }
        }
    }
    return false;
}
bool hungry ()
{
    for (s=1; s<=m+1-mid; s++)
    {//枚举区间起点与终点
        int ans = 0;
        e = s + mid;
        memset (link, 0, sizeof(link));
        for (int i=1; i<=n; i++)
        {
            memset (vis, 0, sizeof(vis));
            if (Find (i))
                ans ++;
        }
        if (ans == n)
            return true;
    }
    return false;
}
int main ()
{
    while (scanf ("%d %d", &n, &m) != EOF)
    {
        for (int i=1; i<=n; i++)
            for (int j=1; j<=m; j++)
            {
                scanf ("%d", &mid);
                maps[i][mid] = j;
            }
        for (int i=1; i<=m; i++)
            scanf ("%d", &limit[i]);
        int left = 1, right = m, ans = 0;
        while (left<=right)
        {//二分枚举区间
            mid = (right+left)/2;
            if (hungry())
                {
                    ans = mid;
                    right = mid - 1;
                }
            else
                left = mid + 1;
        }
        printf ("%d
", ans);
    }
    return 0;
}
View Code

法2.首先二分图匹配,正常情况下,都是单个单个的点这样匹配、
但是像这道题,右边的点一个可以匹配好几个左边的点。也是用匈牙利算法,不过要做少少修改。
枚举答案的时候,有两种方法:
一个是移动区间的方法。复杂度O(B)。注意,只移动区间右边的时候不用重新匹配,只需要接着上次没匹配完的继续匹配就行了
一个是二分法。复杂度 O(B lgB)
由于数据的问题,这两种方法时间相差无几。在 16ms 和 32ms 之间。

ps:这里是枚举棚子,与上面不同

#include <stdio.h>

struct barn {
    int link[1024], cnt, vis, cap;
};
struct cow {
    int rank[32];
};

struct barn barns[32];
struct cow cows[1024];
int start, end, last_pos;
int N, B, tm;

int dfs(int c)
{
    int i, j;
    struct barn *b;

    for (i = start; i <= end; i++) {
        b = &barns[cows[c].rank[i]];
        if (b->vis == tm)
            continue;
        b->vis = tm;
        if (b->cnt < b->cap) {
            b->link[b->cnt++] = c;
            return 1;
        }
        for (j = 0; j < b->cap; j++)
            if (dfs(b->link[j])) {
                b->link[j] = c;
                return 1;
            }
    }
    return 0;
}

inline void init()
{
    int i;

    for (i = 1; i <= B; i++)
        barns[i].cnt = 0;
    last_pos = 1;
}

inline int match()
{
    int i, j;

    for (i = last_pos; i <= N; i++) {
        tm++;
        if (!dfs(i))
            break;
    }
    last_pos = i;
    return i > N;
}

int main()
{
    int i, j, ans;

    //freopen("e:\test\in.txt", "r", stdin);

    scanf("%d%d", &N, &B);
    for (i = 1; i <= N; i++)
        for (j = 1; j <= B; j++)
            scanf("%d", &cows[i].rank[j]);
    for (i = 1; i <= B; i++)
        scanf("%d", &barns[i].cap);

#if 1
    // O(B)
    ans = B;
    start = end = 1;
    while (start <= B && end <= B) {
        init();
        while (end <= B && !match())
            end++;
        if (end - start + 1 < ans)
            ans = end - start + 1;
        start++;
    }
#else
    // O(B*lgB)
    int l, r, m;

    l = 1;
    r = B;
    while (l <= r) {
        m = (l + r) / 2;
        for (start = 1; start <= B - m + 1; start++) {
            end = start + m - 1;
            init();
            if (match())
                break;
        }
        if (start == B - m + 2) {
            // failed
            l = m + 1;
        } else
            r = m - 1;
    }
    ans = r + 1;
#endif

    printf("%d
", ans);

    return 0;
}
View Code

29 / 56 Problem P HDU 2255 奔小康赚大钱

KM算法 模板题

 
里面有解释。
 
网上也好多KM算法的讲解。
 
不管了,会用就行了,套模板
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
using namespace std;

/*  KM算法
 *   复杂度O(nx*nx*ny)
 *  求最大权匹配
 *   若求最小权匹配,可将权值取相反数,结果取相反数
 *  点的编号从0开始
 */
const int N = 310;
const int INF = 0x3f3f3f3f;
int nx,ny;//两边的点数
int g[N][N];//二分图描述
int linker[N],lx[N],ly[N];//y中各点匹配状态,x,y中的点标号
int slack[N];
bool visx[N],visy[N];

bool DFS(int x)
{
    visx[x] = true;
    for(int y = 0; y < ny; y++)
    {
        if(visy[y])continue;
        int tmp = lx[x] + ly[y] - g[x][y];
        if(tmp == 0)
        {
            visy[y] = true;
            if(linker[y] == -1 || DFS(linker[y]))
            {
                linker[y] = x;
                return true;
            }
        }
        else if(slack[y] > tmp)
            slack[y] = tmp;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i = 0;i < nx;i++)
    {
        lx[i] = -INF;
        for(int j = 0;j < ny;j++)
            if(g[i][j] > lx[i])
                lx[i] = g[i][j];
    }
    for(int x = 0;x < nx;x++)
    {
        for(int i = 0;i < ny;i++)
            slack[i] = INF;
        while(true)
        {
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(DFS(x))break;
            int d = INF;
            for(int i = 0;i < ny;i++)
                if(!visy[i] && d > slack[i])
                    d = slack[i];
            for(int i = 0;i < nx;i++)
                if(visx[i])
                    lx[i] -= d;
            for(int i = 0;i < ny;i++)
            {
                if(visy[i])ly[i] += d;
                else slack[i] -= d;
            }
        }
    }
    int res = 0;
    for(int i = 0;i < ny;i++)
        if(linker[i] != -1)
            res += g[linker[i]][i];
    return res;
}
//HDU 2255
int main()
{
    int n;
    while(scanf("%d",&n) == 1)
    {
        for(int i = 0;i < n;i++)
            for(int j = 0;j < n;j++)
                scanf("%d",&g[i][j]);
        nx = ny = n;
        printf("%d
",KM());
    }
    return 0;
}
View Code

20 / 33 Problem Q HDU 3488 Tour

KM

好像费用流也可以

题意:这题自己YY了下,没想到结论还是对的。题目告诉我们一个有向图,现在问将图中的每一个点都划分到一个环中的最少代价是多少?每条边都有一个代价。

解法:由于要成环,那么将这个图进行拆点,就变成了单向的二分图了,此时一个完备匹配就是一种连线策略,只要保证没有边是和自己相连,就能够满足题目中要求的每个点至少属于一个环。证明也是很简单的。因为我们总可以从一个完备匹配中找出起点,然后再从匹配点作为起点找......

左图可以看做是1,2成环,3,4,5成环。

代码如下:

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

int N, M;

const int INF = 0x3f3f3f3f;
int w[205][205];
int lx[205], ly[205];
int sx[205], sy[205];
int match[205], slack[205];

int path(int u) {
    sx[u] = 1;
    for (int i = 1; i <= N; ++i) {
        if (sy[i]) continue;
        int t = lx[u] + ly[i] - w[u][i];
        if (!t) {
            sy[i] = 1;
            if (!match[i] || path(match[i])) {
                match[i] = u;
                return true;
            }
        } else {
            slack[i] = min(slack[i], t);
        }
    }
    return false;
}

void KM() {
    memset(match, 0, sizeof (match));
    memset(lx, 0x80, sizeof (lx));
    memset(ly, 0, sizeof (ly));
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= N; ++j) { 
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= N; ++i) {
        memset(slack, 0x3f, sizeof (slack));
        while (1) {
            memset(sx, 0, sizeof (sx));
            memset(sy, 0, sizeof (sy));
            if (path(i)) break;
            int d = INF;
            for (int j = 1; j <= N; ++j) {
                if (!sy[j]) d = min(d, slack[j]);
            }
            for (int j = 1; j <= N; ++j) {
                if (sx[j]) lx[j] -= d;
                if (sy[j]) ly[j] += d;
                else slack[j] -= d;
            }
        }
    }
    int ret = 0;
    for (int i = 1; i <= N; ++i) {
        ret += w[match[i]][i];
    }
    printf("%d
", -ret);
}

int main() {
    int T, x, y, ct;
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &N, &M);
        memset(w, 0x80, sizeof (w));
        for (int i = 1; i <= M; ++i) {
            scanf("%d %d %d", &x, &y, &ct);
            w[x][y] = max(w[x][y], -ct);
        }
        KM();
    }
    return 0;    
}
View Code

10 / 20 Problem R URAL 1099 Work Scheduling

一般图匹配带花树

模板题!!!

以下是模板:

/* ***********************************************
Author        :kuangbin
Created Time  :2013/8/21 22:56:05
File Name     :F:2013ACM练习专题学习图论一般图匹配带花树URAL1099.cpp
************************************************ */

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;

const int MAXN = 250;
int N; //点的个数,点的编号从1到N
bool Graph[MAXN][MAXN];
int Match[MAXN];
bool InQueue[MAXN],InPath[MAXN],InBlossom[MAXN];
int Head,Tail;
int Queue[MAXN];
int Start,Finish;
int NewBase;
int Father[MAXN],Base[MAXN];
int Count;//匹配数,匹配对数是Count/2
void CreateGraph()
{
    int u,v;
    memset(Graph,false,sizeof(Graph));
    scanf("%d",&N);
    while(scanf("%d%d",&u,&v) == 2)
    {
        Graph[u][v] = Graph[v][u] = true;
    }
}
void Push(int u)
{
    Queue[Tail] = u;
    Tail++;
    InQueue[u] = true;
}
int Pop()
{
    int res = Queue[Head];
    Head++;
    return res;
}
int FindCommonAncestor(int u,int v)
{
    memset(InPath,false,sizeof(InPath));
    while(true)
    {
        u = Base[u];
        InPath[u] = true;
        if(u == Start) break;
        u = Father[Match[u]];
    }
    while(true)
    {
        v = Base[v];
        if(InPath[v])break;
        v = Father[Match[v]];
    }
    return v;
}
void ResetTrace(int u)
{
    int v;
    while(Base[u] != NewBase)
    {
        v = Match[u];
        InBlossom[Base[u]] = InBlossom[Base[v]] = true;
        u = Father[v];
        if(Base[u] != NewBase) Father[u] = v;
    }
}
void BloosomContract(int u,int v)
{
    NewBase = FindCommonAncestor(u,v);
    memset(InBlossom,false,sizeof(InBlossom));
    ResetTrace(u);
    ResetTrace(v);
    if(Base[u] != NewBase) Father[u] = v;
    if(Base[v] != NewBase) Father[v] = u;
    for(int tu = 1; tu <= N; tu++)
        if(InBlossom[Base[tu]])
        {
            Base[tu] = NewBase;
            if(!InQueue[tu]) Push(tu);
        }
}
void FindAugmentingPath()
{
    memset(InQueue,false,sizeof(InQueue));
    memset(Father,0,sizeof(Father));
    for(int i = 1;i <= N;i++)
        Base[i] = i;
    Head = Tail = 1;
    Push(Start);
    Finish = 0;
    while(Head < Tail)
    {
        int u = Pop();
        for(int v = 1; v <= N; v++)
            if(Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v))
            {
                if((v == Start) || ((Match[v] > 0) && Father[Match[v]] > 0))
                    BloosomContract(u,v);
                else if(Father[v] == 0)
                {
                    Father[v] = u;
                    if(Match[v] > 0)
                        Push(Match[v]);
                    else
                    {
                        Finish = v;
                        return;
                    }
                }
            }
    }
}
void AugmentPath()
{
    int u,v,w;
    u = Finish;
    while(u > 0)
    {
        v = Father[u];
        w = Match[v];
        Match[v] = u;
        Match[u] = v;
        u = w;
    }
}
void Edmonds()
{
    memset(Match,0,sizeof(Match));
    for(int u = 1; u <= N; u++)
        if(Match[u] == 0)
        {
            Start = u;
            FindAugmentingPath();
            if(Finish > 0)AugmentPath();
        }
}
void PrintMatch()
{
    Count = 0;
    for(int u = 1; u <= N;u++)
        if(Match[u] > 0)
            Count++;
    printf("%d
",Count);
    for(int u = 1; u <= N; u++)
        if(u < Match[u])
            printf("%d %d
",u,Match[u]);
}
int main()
{
    CreateGraph();//建图
    Edmonds();//进行匹配
    PrintMatch();//输出匹配数和匹配
    return 0;
}
View Code

7 / 22 Problem S HDU 4687 Boke and Tsukkomi

一般图匹配带花树

此题求哪些边在任何一般图极大匹配中都无用,对于任意一条边i,设i的两个端点分别为si,ti,

则任意一个极大匹配中都必然有si或ti至少一个点被匹配,当在图中去掉si,ti两个点时,匹配数会损失一个或两个.

如果损失两个,就说明在极大匹配中这两个点分别连接不同的边,于是边i是无用的

所以总体思路:一般图匹配求出最大匹配数cnt0,分别试着去掉每条边的端点,再次匹配,匹配数如果小于cnt0-1,则这条边无用,记录

/* ***********************************************
Author        :kuangbin
Created Time  :2013/8/23 19:28:08
File Name     :F:2013ACM练习专题学习图论一般图匹配带花树HDU4687.cpp
************************************************ */

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int MAXN = 50;
int N; //点的个数,点的编号从1到N
bool Graph[MAXN][MAXN];
int Match[MAXN];
bool InQueue[MAXN],InPath[MAXN],InBlossom[MAXN];
int Head,Tail;
int Queue[MAXN];
int Start,Finish;
int NewBase;
int Father[MAXN],Base[MAXN];
int Count;
void Push(int u)
{
    Queue[Tail] = u;
    Tail++;
    InQueue[u] = true;
}
int Pop()
{
    int res = Queue[Head];
    Head++;
    return res;
}
int FindCommonAncestor(int u,int v)
{
    memset(InPath,false,sizeof(InPath));
    while(true)
    {
        u = Base[u];
        InPath[u] = true;
        if(u == Start) break;
        u = Father[Match[u]];
    }
    while(true)
    {
        v = Base[v];
        if(InPath[v])break;
        v = Father[Match[v]];
    }
    return v;
}
void ResetTrace(int u)
{
    int v;
    while(Base[u] != NewBase)
    {
        v = Match[u];
        InBlossom[Base[u]] = InBlossom[Base[v]] = true;
        u = Father[v];
        if(Base[u] != NewBase) Father[u] = v;
    }
}
void BloosomContract(int u,int v)
{
    NewBase = FindCommonAncestor(u,v);
    memset(InBlossom,false,sizeof(InBlossom));
    ResetTrace(u);
    ResetTrace(v);
    if(Base[u] != NewBase) Father[u] = v;
    if(Base[v] != NewBase) Father[v] = u;
    for(int tu = 1; tu <= N; tu++)
        if(InBlossom[Base[tu]])
        {
            Base[tu] = NewBase;
            if(!InQueue[tu]) Push(tu);
        }
}
void FindAugmentingPath()
{
    memset(InQueue,false,sizeof(InQueue));
    memset(Father,0,sizeof(Father));
    for(int i = 1;i <= N;i++)
        Base[i] = i;
    Head = Tail = 1;
    Push(Start);
    Finish = 0;
    while(Head < Tail)
    {
        int u = Pop();
        for(int v = 1; v <= N; v++)
            if(Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v))
            {
                if((v == Start) || ((Match[v] > 0) && Father[Match[v]] > 0))
                    BloosomContract(u,v);
                else if(Father[v] == 0)
                {
                    Father[v] = u;
                    if(Match[v] > 0)
                        Push(Match[v]);
                    else
                    {
                        Finish = v;
                        return;
                    }
                }
            }
    }
}
void AugmentPath()
{
    int u,v,w;
    u = Finish;
    while(u > 0)
    {
        v = Father[u];
        w = Match[v];
        Match[v] = u;
        Match[u] = v;
        u = w;
    }
}
void Edmonds()
{
    memset(Match,0,sizeof(Match));
    for(int u = 1; u <= N; u++)
        if(Match[u] == 0)
        {
            Start = u;
            FindAugmentingPath();
            if(Finish > 0)AugmentPath();
        }
}
int getMatch()
{
    Edmonds();
    Count = 0;
    for(int u = 1; u <= N;u++)
        if(Match[u] > 0)
            Count++;
    return Count/2;
}

bool g[MAXN][MAXN];
pair<int,int>p[150];
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int m;
    while(scanf("%d%d",&N,&m)==2)
    {
        memset(g,false,sizeof(g));
        memset(Graph,false,sizeof(Graph));
        int u,v;
        for(int i = 1;i <= m;i++)
        {
            scanf("%d%d",&u,&v);
            p[i] = make_pair(u,v);
            g[u][v] = true;
            g[v][u] = true;
            Graph[u][v] = true;
            Graph[v][u] = true;
        }
        int cnt0 = getMatch();
        //cout<<cnt0<<endl;
        vector<int>ans;
        for(int i = 1;i <= m;i++)
        {
            u = p[i].first;
            v = p[i].second;
            memcpy(Graph,g,sizeof(g));
            for(int j = 1;j <= N;j++)
                Graph[j][u] = Graph[u][j] = Graph[j][v] = Graph[v][j] = false;
            int cnt = getMatch();
            //cout<<cnt<<endl;
            if(cnt < cnt0-1)
                ans.push_back(i);
        }
        int sz = ans.size();
        printf("%d
",sz);
        for(int i = 0;i < sz;i++)
        {
            printf("%d",ans[i]);
            if(i < sz-1)printf(" ");
        }
        printf("
");
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/gongpixin/p/5360660.html