poj 3715 Blue and Red(二分图最大匹配匈牙利算法)

【题目大意】:给出n个士兵和m组关系,每个士兵均被划分在Blue组或者是Red组,m组关系描述的是士兵x,y直接是否是亲密关系。由于具备亲密关系的两个士兵被分在两个不同的组,那么对训练结果会产生不好的影响,问你最少去掉多少个士兵能够避免掉这种影响。并输出字典序最小的方案。

【解题思路】:题目一看下来,第一感觉就是写个二分图的最大匹配,所以一开始加了个源点和汇点就敲了个网络流。最大匹配是搞定了~问题是方案不会搞,所以yy了一下~想想自己没写过匈牙利算法就试一下吧,顺便学学。匈牙利算法是利用dfs寻找增广路径来确定最大匹配的。因为题目要求的是字典序最小,因此我们不妨从0~n-1进行枚举,删除该点后对新的图跑一次匈牙利算法,得到的新的增广路,观察新的增广路径是否变小,若变小则说明此点必在答案中。

【匈牙利算法】:摘自网络:

匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。

假设:M是G的一个匹配。

  M-交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条M-交错路。

  M-饱和点:对于v∈V(G),如果v与M中的某条边关联,则称v是M-饱和点,否则称v是非M-饱和点。
  M-可增广路:p是一条M-交错路,如果p的起点和终点都是非M-饱和点,则称p为M-可增广路。(不要和流网络中的增广路径弄混了)
增广路的定义(也称增广轨或交错轨):
  若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
  由增广路的定义可以推出下述三个结论:
  1-P的路径个数必定为奇数,第一条边和最后一条边都不属于M。
  2-将M和P进行取反操作可以得到一个更大的匹配M’。
  3-M为G的最大匹配当且仅当不存在M的增广路径。
时间复杂度 邻接矩阵:最坏为O(n^3) 邻接表:O(mn)  空间复杂度 邻接矩阵:O(n^2) 邻接表:O(m+n)
【代码】:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

#define maxn 300
#define maxm 21000

struct edge{
	int u,v;
	int next;
};
int eh[maxn],tot,ans;
int T,n,m;
edge et[maxm];
bool visit[maxn];
int match[maxn];
int po[maxn];

void addd(int u,int v){
	edge E = {u,v,eh[u]};
	et[tot] = E, eh[u] = tot++;
}
void addedge(int u,int v){
	addd(u,v);
}

bool dfs(int u) {
    for (int i = eh[u]; i != -1; i = et[i].next){
        if (!visit[et[i].v] && po[et[i].v]==1){
            visit[et[i].v]=true;
             if (match[et[i].v] == -1 || dfs(match[et[i].v])) {
                match[et[i].v] = u;
                return true;
            }
        }
    }
    return false;
}
int Match() {
    int cnt = 0;
    memset(match, -1, sizeof (match));
    for (int u = 0; u < n; u++) {
        memset(visit, false, sizeof (visit));
        if (!po[u] && dfs(u)) cnt++;
    }
    return cnt;
}

void solve(){
    int anss[maxn];
    int cnt=0;
    for(int i=0; i<n; i++){
        po[i]=po[i]^1;
        int tmp=Match();
        if(tmp<ans){
            ans=tmp;
            anss[cnt++]=i;
        }
        else{
            po[i]=po[i]^1;
        }
    }
    printf("%d",cnt);
    for(int i=0;i<cnt;i++){
        printf(" %d",anss[i]);
    }
    printf("\n");
}

int main(){
    scanf("%d", &T);
    while (T--){
        scanf("%d%d",&n,&m);
        int tmp;
        for (int i=0; i<n; i++){
            scanf("%d",&tmp);
            po[i]=tmp;
        }
        int x,y;
        tot=0;
        memset(eh,-1,sizeof(eh));
        for (int i=0; i<m; i++){
            scanf("%d%d",&x,&y);
            if (po[x]!=po[y]){
                if (po[x]==0) addedge(x,y);
                else addedge(y,x);
            }
        }
        ans=Match();
        solve();
    }

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值