HDU3718(最大权完美匹配)

题意:先给出N个字母,代表要标签;再给出M行,和第一行给出的N个标签进行匹配,之间的差代表权值。最后的相似度为最大权值为ans*1.0/n.

最大权完美匹配:二分图最大匹配是寻找最大匹配数,用匈牙利算法。当连 接的边带有权值时,要寻找匹配后权值和最大的方案,且保证 A 集合中的点均有 B 中的点能匹配。此时问题就转化为二分图最大权完美匹配。

KM 算法核心为: 为每一点添加顶标, 在顶标的限制下用匈牙利算法处理出最大匹配数, 若最大匹配数 =n, 则达到最优解, 输出。否则修改
顶标, 再用匈牙利算法处理, 如此重复。

设二分图的两部分点集分别为 X={X1,X2,…,Xn}X={X1,X2,…,Xn} 和 Y={Y1,Y2,…,Ym}Y={Y1,Y2,…,Ym}, ⟨Xi,Yj⟩⟨Xi,Yj⟩ 的边权为 wij
给两部分点集分别赋点权 {Ai},{Bi}{Ai},{Bi}, 使得 Ai+Bj⩾wij,取等的边的生成子图叫做相等子图。那么相等子图的完美匹配就是最大权匹配。我们需要适当选取权值,使相等子图有完美匹配。
算法步骤:
1.若成功(找到了增广轨),则该点增广完成,进入下一个点的增广
2.若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。
操作为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全 部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<iomanip>
using namespace std;
const int maxx=105;
const int maxn=10005;
const int inf=0x3f3f3f3f;
int lx[maxx],ly[maxx];
int visx[maxx],visy[maxx];
int w[maxx][maxx];
int linker[maxx];
int slack[maxx];
int n,m,k;
char ch[5];
char p[maxn];
char p1[maxn];
void init(){
	memset(linker,0,sizeof(linker));
	memset(w,0,sizeof(w));
}
int Find(int x){
	visx[x]=1;
	for(int y=1;y<=26;y++){
		if(visy[y]==0){
			int temp=lx[x]+ly[y]-w[x][y];
			if(temp==0){
				visy[y]=1;
				if(linker[y]==0||Find(linker[y])){
					linker[y]=x;
					return 1;
				}
			}else{
				slack[y]=min(slack[y],temp);
			}
		}
	}
	return 0;
}
int KM(){
	memset(ly,0,sizeof(ly));
	for(int i=1;i<=26;i++){
		lx[i]=-inf;
	}
	for(int i=1;i<=26;i++){
		for(int j=1;j<=26;j++){
			if(lx[i]<w[i][j]){
				lx[i]=w[i][j];
			}
		}
	}
	for(int k=1;k<=26;k++){
		for(int i=1;i<=26;i++){
			slack[i]=inf;
		}
		while(true){
			memset(visx,0,sizeof(visx));
			memset(visy,0,sizeof(visy));
			if(Find(k))break;
			int d=inf;
			for(int i=1;i<=26;i++){
				if(visy[i]==0){
					d=min(d,slack[i]);
				}
			}
			if(d==inf)return 0;
			for(int i=1;i<=26;i++){
				if(visx[i]==1)lx[i]-=d;
				if(visy[i]==1)ly[i]+=d;
				else{
					slack[i]-=d;
				}
			}
		}
	}
	int ans=0;
	for(int i=1;i<=26;i++){
		if(linker[i]!=0){
			ans+=w[linker[i]][i];
		}
	}
	return ans;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d %d",&n,&k,&m);
		init();
		for(int i=1;i<=n;i++){
			scanf("%s",&ch);
			p[i]=ch[0];
		}
		for(int i=1;i<=m;i++){
			init();
			for(int j=1;j<=n;j++){
				scanf("%s",ch);
				p1[j]=ch[0];
				w[p[j]-'A'+1][p1[j]-'A'+1]++;
			}
			int ans=KM();
			cout<<setiosflags(ios::fixed)<<setprecision(4)<<1.0*ans/n<<endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值