HDU3718/ZOJ3425 Similarity(The 2010 ACM-ICPC Asia Chengdu Regional Contest,加权二分图的最优匹配)

只要能看出来是二分图的最优匹配,然后建图,套模板,应该就能过了,几乎是裸模板题,听 亚伟 说去年现场赛的时候小伟他们1Y过掉这个题,然后就尝试了下,的确不是很难,这又给了我们弱小心灵一点点希望和安慰……

我用的是 KM算法 ,调整顶标值加上一直 dfs 找增广路(就是二分图最大匹配的匈牙利算法),具体解释参见:

http://hi.baidu.com/wwbmmm/blog/item/7f356cfa92d23962034f56e5.html

PS:不要光看博文,下面有个牛牛的评论,相当给力!!!

我的代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<climits>
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
const int N = 30;

struct point{
	char name;
	int id;
};

point pa[N],pb[N];

int map[N][N],len,g,stu;

void addpoint_a(char x,int &k1){//构造二分图的x点集
	int i,add=1;
	for(i=1;i<k1;i++){
		if(x==pa[i].name){
			add=0;break;
		}
	}
	if(add){
		pa[k1].name=x;
		pa[k1].id=k1;
		k1++;
	}
}

void addpoint_b(char x,int &k2){//构造二分图的y点集
	int i,add=1;
	for(i=1;i<k2;i++){
		if(x==pb[i].name){
			add=0;break;
		}
	}
	if(add){
		pb[k2].name=x;
		pb[k2].id=k2;
		k2++;
	}
}

int searchx(char x,point p[]){
	for(int i=1;i<=g;i++){
		if(p[i].name==x)return p[i].id;
	}
	return -1;
}

int lx[N],ly[N],match[N],visx[N],visy[N],lack;

int dfs(int x){//匈牙利
	visx[x]=1;
	for(int i=1;i<=g;i++){
		if(!visy[i] && lx[x]+ly[i]==map[x][i]){
			visy[i]=1;
			if(match[i]==-1 || dfs(match[i])){
				match[i]=x;
				return 1;
			}
		}
	}
	return 0;
}

int KM(){
	int i,j,k;
	for(i=1;i<=g;i++){//初始化顶标
		lx[i]=INT_MIN; ly[i]=0;
		for(j=1;j<=g;j++)
			lx[i]=max(map[i][j],lx[i]);
	}
	memset(match,-1,sizeof(match));
	for(i=1;i<=g;i++){
		while(1){//直到每个点都成功匹配,才结束循环
			memset(visx,0,sizeof(visx));
			memset(visy,0,sizeof(visy));
			if(dfs(i))break; //如果点 i 成功匹配,则不需要调整定标值

			/*------------顶标调整------------*/
			int lack=INT_MAX;//顶标调整值
			for(j=1;j<=g;j++){
				if(visx[j]){
					for(k=1;k<=g;k++){//取幅度最小的值为调整值
						if(!visy[k])lack=min(lx[j]+ly[k]-map[j][k],lack);
					}
				}
			}
			for(j=1;j<=g;j++){//调整已经得到匹配的点对的顶标值
				if(visx[j])lx[j]-=lack;
				if(visy[j])ly[j]+=lack;
			}
			/*---------------------------------*/
		}
	}
	int sum=0;
	for(i=1;i<=g;i++){
		if(match[i]!=-1)sum+=map[match[i]][i];
	}
	return sum;
}

void solve()
{
	int i,j;
	char str[10010],ch[3];
	
	scanf("%d%d%d",&len,&g,&stu);
	int k1=1;
	for(i=1;i<=len;i++){
		scanf("%s",ch);
		str[i]=ch[0];
		addpoint_a(str[i],k1);
	}
	
	for(i=0;i<stu;i++){
		int k2=1;
		memset(map,0,sizeof(map));
		memset(pb,0,sizeof(pb));
		for(j=1;j<=len;j++){
			scanf("%s",ch);
			addpoint_b(ch[0],k2);
			int a=searchx(str[j],pa);
			int b=searchx(ch[0],pb);
			map[a][b]++;
		}
		int ans=KM();
		printf("%.4lf\n",ans*1.0/len);	
	}
}

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		solve();
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值