【BzoJ 1601】【灌水】【最小生成树】【贪心决策】【并查集】


1601: [Usaco2008 Oct]灌水

Time Limit: 5 Sec   Memory Limit: 162 MB
Submit: 2103   Solved: 1384
[ Submit][ Status][ Discuss]

Description

Farmer John已经决定把水灌到他的n(1<=n<=300)块农田,农田被数字1到n标记。把一块土地进行灌水有两种方法,从其他农田饮水,或者这块土地建造水库。 建造一个水库需要花费wi(1<=wi<=100000),连接两块土地需要花费Pij(1<=pij<=100000,pij=pji,pii=0). 计算Farmer John所需的最少代价。

Input

*第一行:一个数n

*第二行到第n+1行:第i+1行含有一个数wi

*第n+2行到第2n+1行:第n+1+i行有n个被空格分开的数,第j个数代表pij。

Output

*第一行:一个单独的数代表最小代价.

Sample Input

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

Sample Output

9


输出详解:

Farmer John在第四块土地上建立水库,然后把其他的都连向那一个,这样就要花费3+2+2+2=9

HINT

Source

[ Submit][ Status][ Discuss]

HOME  Back

一道十分有趣的题目呢~


这道题我们肯定可以贪心的想到最后构成的肯定是 [树 / 森林];因为若是构成了有环图的话,则肯定可以删去一条边使其依然连通 ,所以有环的无向图一定不优,那边只能是无环无向图了(其实就是树);

那么这道题就可以去往最小生成树的方面去想了,但本题有一个特殊的在于由于可以在某些田地里建水塔,则有可能建出来的是一片森林而不是树(森林中的每一个树都有一个水塔点);那么最简单的最小生成树便不一定是答案了不是么?

那咋办?根据树的性质和最小生成树的特点便会有一种谈心的解法:

对于并查集的集合来说,我们当其为一个独立联通并有水灌溉的树,记录下其最小代价和在该最小代价下的水塔代价(可以简单的贪心证明每个块只需一个水塔即可)对于在最小生成树时,我们对于判断是否选择该边除了看边联通的两点是否在同一集合内还要多附加一个条件:该边的代价是否比联通的两个集合中那个较大的水塔代价小,若是则合并并算出新的集合的代价:集合A的代价 + 集合B的代价 + 联通边的代价 - max(集合A水塔代价 , 集合B水塔代价);合并的集合的水塔代价为min(集合A水塔代价 , 集合B水塔代价);

代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 110001
using namespace std;
long long n , lon_;
long long p[M] , w[M] , st[M];
long long s[M] , e[M] , lon[M] , ran[M] , cnt;
bool cmp(const long long &a , const long long &b){return lon[a] < lon[b];}
long long find(long long x){return x == p[x] ? x : p[x] = find(p[x]); }
int main(){
	scanf("%lld",&n);
	for(long long i = 1 ; i <= n ; ++i){
		scanf("%lld",&w[i]);
		st[i] = w[i];
		p[i] = i;
	}
	for(long long i = 1 ; i <= n ; ++i)
		for(long long j = 1 ; j <= n ; ++j){
			scanf("%lld",&lon_);
			if(i < j){
				cnt++;
				s[cnt] = i , e[cnt] = j , lon[cnt] = lon_ , ran[cnt] = cnt;
			}
		}
	sort(ran + 1 , ran + 1 + cnt , cmp);
	for(long long i = 1 ; i <= cnt ; ++i){
		long long v = ran[i];
		long long x = find(s[v]) , y = find(e[v]);
		if(x != y && lon[v] < max(st[x] , st[y])){
			if(st[x] > st[y])swap(x , y);
			p[y] = x;
			w[x] = w[x] + w[y] - st[y] + lon[v];
		}
	}
	long long ans = 0;
	for(long long i = 1 ; i <= n ; ++i)
		if(p[i] == i)
			ans += w[i];

	printf("%lld",ans);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值