poj3273 最大生成树

刚开始的想法:

用数组usedman和usedwoman来检查顶点的使用情况

对边进行排序,然后检查这条边的from和to,如果from和to都没有使用,那么ans+=20000-costr

如果from使用了 那么used【to】=true,ans+=10000-cost   to同理

WA了。。

</pre><pre name="code" class="cpp">#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=10010;
bool usedman[maxn];
bool usedwoman[maxn];
typedef struct {
	int  a,b,cost;
}edge;
edge E[5*maxn];
bool cmp(edge a,edge b){
	return a.cost > b.cost;
}
int main(int argc, char const *argv[])
{
	int N,M,R;
	int T;
	cin>>T;
	while(T--){
		memset(usedwoman,false,sizeof(usedwoman));
		memset(usedman,false,sizeof(usedman));
		cin>>N>>M>>R;
		for(int i=0;i<R;i++){
			scanf("%d%d%d",&E[i].a,&E[i].b,&E[i].cost);
		}
		sort(E,E+R,cmp);
		int ans=0;
		for(int i=0;i<R;i++){
			int girl=E[i].a,boy=E[i].b,cost=E[i].cost;
			if(!usedwoman[girl] && !usedman[boy]){
				usedwoman[boy]=usedwoman[girl]=true;
				ans+=20000-cost;
			}
			else if(!usedwoman[girl]){
				usedwoman[girl]=true;
				ans+=10000-cost;
			}
			else if(!usedman[boy]){
				usedman[boy]=true;
				ans+=10000-cost;
			}
		}
		for(int i=0;i<N;i++){
			if(!usedwoman[i]) ans+=10000;
		}
		for(int i=0;i<M;i++){
			if(!usedman[i]) ans+=10000;
		}
		printf("%d\n",ans);
	}
	return 0;
}



但是实际上,这个关系可以看成是单向的,但是似乎并没有什么问题。。

网上认为这个问题实质上是最大生成树问题,如果以上算法是错的,那么只能说是因为忽略了一些情况导致结果较大。


两个小时才找到问题:

我这样做是把边看做没有方向的,实际上边应该看做有方向的,方向指向的应该是后参加的人,因为只有后参加的人才能得到优惠。

我这样做对于不同的树合并是没有差别的,同一颗树下面的叶子不可能合并。而且,不同的树也不可能合并,但是这个合并能否减少总的费用呢?

这是肯定的,因为不同的树之间的合并可以减少树根的费用。所以还是有必要用并查集来检查是不是同一颗树的(还要明确并不是只有树根连接才能得到优化,因为两个人相连的时候,把谁当做root这个问题还没有确定,那么我们相连的时候,不管谁是root,只要不是同一颗树,都能够得到优化!(比如两棵树的叶子相连,把它们的根连在一起同样可以得到优化)

最后还是转为了最大生成树问题

<pre name="code" class="cpp">#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=50010;
int pre[2*maxn];
struct edge
{
	int a,b,cost;
}e[5*maxn];
int V,T,N,M,R;
bool cmp(edge a,edge b){
	return a.cost > b.cost;
}
int find(int p){
	return pre[p]==p?p:pre[p]=find(pre[p]);
}
bool connect(int p,int q){
	return find(p)==find(q);
}
void join(int p,int q){
	int rootp=find(p);
	int rootq=find(q);
	if(rootq!=rootp) pre[rootp]=rootq;
}

int kruscal(){
	int ans=0;
	for(int i=0;i<V;i++) pre[i]=i;
	sort(e,e+R,cmp);
	for(int i=0;i<R;i++){
		int first=e[i].a,second=e[i].b;
		if(!connect(first,second)){
			join(first,second);
			ans+=e[i].cost;
		}
	}
	return ans;
}

int main(int argc, char const *argv[])
{
	cin>>T;
	while(T--){
		cin>>N>>M>>R;
		V=N+M;
		for(int i=0;i<R;i++){
			scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].cost);
			e[i].b+=N;
		}
		printf("%d\n",(N+M)*10000-kruscal());
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值