zoj3933 最佳完美匹配 KM算法

这道题目花了我很长时间进行思考和找BUG,刚开始以为可以通过匈牙利算法(最大匹配)加上排序可以算出,但发现并不是这样的,将女生放在前面优先匹配也不一定得到女生最多的匹配,因为匈牙利算法一直从头开始取,就可能漏取许多女生,让前面的女生取到后面男生配对了。于是发现了另一个算法,KM算法,可以把女生的权值比男生大,到时候就会优先连接女生了,就可以得出女生最多的解,但是KM算法必须要完美匹配的状态下,所以可以将不相连的权值设为一个男女生权值都不可能达到的值,到时候找不到相连的就可以连接这些点,到时候输出的时候注意一点就可以了。。。。

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int INF=0x3f3f3f3f;//整数int的最大值
int num[505][505];//记录连接的权值
int index1[505][505];//判断是否可以相连
int slack[505];//保存第二组中的成员连接到任意一个第一组的成员所需要提高的权值
int l1[505],l2[505];//l1表示第一组成员连接到第二组任意一个成员的最大值,l2表示第二组成员的权值,起始值为0
int vis0[505],vis1[505];//表示第一组和第二组是否被访问过 
int n;
int match[505];//第二组成员与第一组的配对
bool dfs(int x){
	vis0[x]=1;
	for(int i=1;i<=n;i++){
		if(vis1[i]!=1){
			int right=l1[x]+l2[i]-num[x][i];
			if(right==0){
				vis1[i]=1;
				if(match[i]==0||dfs(match[i])){
					match[i]=x;
					return true;
				}
			}
			else{
				if(slack[i]>right){
					slack[i]=right;
				}
			}
			
		}
	}
	return false;
}
void km(){
	memset(l2,0,sizeof(l2));
	memset(match,0,sizeof(match));
	for(int i=1;i<=n;i++){
		l1[i]=-INF;
		for(int j=1;j<=n;j++){
			if(l1[i]<num[i][j]){
				l1[i]=num[i][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		fill(slack,slack+n+1,INF);//要放在循环里面; 因为间隔不一样,比如起始最大期望是五,他还有两个期望分别为4和2,那第一次slack最小值为1,那第二次就是2了,所以每次都不一样,需要在每次开始前都初始化一次 
		while(1){
			memset(vis0,0,sizeof(vis0));
			memset(vis1,0,sizeof(vis1));
			if(dfs(i))
				break;
			else{
				int d=INF;
				for(int j=1;j<=n;j++){
					if(d>slack[j]&&vis1[j]!=1){
						d=slack[j];
					}
				}
				for(int j=1;j<=n;j++){
					if(vis0[j]==1)
						l1[j]-=d;
					if(vis1[j]==1){//因为没有被连上的降低期望后就能被连上,比如上面说的那个例子,第一次slack[i]=1,第二次slack[j]=2,但是第二次是时候slack[i]肯定是被访问过的,所以slack[i]不会影响第二次取到slack的最小值 
						l2[j]+=d;
					}
					else{
						slack[j]-=d;
					}	
				}
			}
		}
	}
	int result=0,mm=0;
	for(int i=1;i<=n;i++){
			if(match[i]!=0&&num[match[i]][i]!=0){//条件不能缺少 
				result++;
				mm+=num[match[i]][i]%10000;
			}
	}
	cout<<result<<" "<<mm<<endl;
	for(int i=1;i<=n;i++){
		if(match[i]!=0&&num[match[i]][i]!=0)//条件不能缺少 
			cout<<match[i]<<" "<<i<<endl;
	}
}
int main(){
	int T;
	string str1,str2;
	cin>>T;
	while(T--){
		memset(num,0,sizeof(num));
		memset(index1,0,sizeof(index1));
		cin>>n;
		cin>>str1>>str2;
		for(int i=1;i<=n;i++){
			int temp1,temp2;
			cin>>temp1;
			for(int j=0;j<temp1;j++){
				cin>>temp2;
				index1[i][temp2]=-1;
				index1[temp2][i]=-1;
			}
		}
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				if(index1[i+1][j+1]!=-1&&index1[j+1][i+1]!=-1&&str1[i]!=str1[j]){
					int temp=10000;
					if(str2[i]=='0')
						temp++;
					if(str2[j]=='0')
						temp++;
					if(str1[i]=='1'&&str1[j]=='0')
						num[j+1][i+1]=temp;
					else
						num[i+1][j+1]=temp;
				}
			}
		}
		km();	
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值