算法训练 安慰奶牛

问题描述

Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路。道路被用来连接N个牧场,牧场被连续地编号为1到N。每一个牧场都是一个奶牛的家。FJ计划除去P条道路中尽可能多的道路,但是还要保持牧场之间 的连通性。你首先要决定那些道路是需要保留的N-1条道路。第j条双向道路连接了牧场Sj和Ej(1 <= Sj <= N; 1 <= Ej <= N; Sj != Ej),而且走完它需要Lj的时间。没有两个牧场是被一条以上的道路所连接。奶牛们非常伤心,因为她们的交通系统被削减了。你需要到每一个奶牛的住处去安慰她们。每次你到达第i个牧场的时候(即使你已经到过),你必须花去Ci的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上 起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的 交谈任务。假设Farmer John采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。

输入格式

第1行包含两个整数N和P。

接下来N行,每行包含一个整数Ci

接下来P行,每行包含三个整数Sj, Ej和Lj

输出格式
输出一个整数, 所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)。
样例输入
5 7
10
10
20
6
30
1 2 5
2 3 5
2 4 12
3 4 17
2 5 15
3 5 6
样例输出
176
数据规模与约定

5 <= N <= 10000,N-1 <= P <= 100000,0 <= Lj <= 1000,1 <= Ci <= 1,000。


先纠正一下题目,我是把第一行的5,7改为5,6。因为后面只输入了6条路。这样实际输出应该是178.


先来分析一下这道题:这道题第一感觉就是最小生成树问题,但是也不是直接能做的那种,因为题目里说到一个牧场都要和奶牛交流。这相当于是顶点有一个值,传统的最小生成树顶点为0,即只有弧是有权值的。那我们的想法是把定点的值和弧的值能成为统一的权值。





这是根据题目要求画出的图。



这里我们先假设已经找到了最小生成树,看看行走规律。

我们以这个格式说明1(1)这表示1处已经和奶牛交谈过依次    我们比如从1出发

1(1)---- 2(1)----5(1)----2(1+1)----3(1)----4(1)----3(1+1)----2(1+1+1)----1(1+1)

我们统计一下1是两次,2是三次,3是两次,4和5各一次。

还有就是我们每一条弧我们来回走了一次,所以每一条弧是两次。

我们可以发现顶点的次数和这个顶点的度有关(度是多少,次数就是几次,这里先不讨论起点多的那一次)。

那我们整体的花费:我们以弧为单位看一下,总体=∑(弧值*2+弧两端的顶点值)+一个起点的顶点值。

所以我们在求出最小生成树选择起点是直接选择顶点值最小的那个即可。

那再来说说如何选择最小生成树。上面的公式已经很明显告诉我们现在的,把原来的弧权值更新为弧值*2+弧两端的顶点值。顶点值也已经考虑进去,这时就变成了传统的最小生成数问题了。



代码:

#include <iostream>
#include<algorithm>
#include<climits>
using namespace std;
#define INF INT_MAX
int Count;//记录已经选入弧的数量 
int Nodes[10010];
typedef struct{
int u;
int v;
int cost;
}Arc;
Arc Arc_vec[100010];
int mark[100010];//存放所在树的信息 
int compare(Arc A,Arc B){
return A.cost<B.cost; 
}
int try_insert(int uu,int vv,int num_Node){//判断uu,vv是否在一棵树并完成合并树操作 
if(mark[uu]==mark[vv])//在同一棵树 
return 0;
int old=mark[uu] ;//不在一棵树就把节点uu所在的树改为新的树(vv所在的树) ,同时把和uu所在同一颗树的节点找出来,改为新的树 (vv所在的树)
for(int i=1;i<=num_Node;i++){
if(mark[i]==old) mark[i]=mark[vv];
}
return 1;
}
int func(int num_Node,int num_Arc){
int cost_sum=0;
sort(Arc_vec,Arc_vec+num_Arc,compare);
for(int i=0;i<num_Arc;i++){
int uu,vv;
uu=Arc_vec[i].u;
vv=Arc_vec[i].v;
if(try_insert(uu,vv,num_Node)) {//如果插入成功,则弧的数量加一 
Count++;
cost_sum+=Arc_vec[i].cost;
}
if(Count==num_Node-1) break;//弧的数量最终为节点数量-1 
}
return cost_sum;
}
int main(int argc, char *argv[]) {
int num_Node,num_Arc;
int Min=INF;
cin>>num_Node>>num_Arc;
for(int i=1;i<=num_Node;i++){
cin>>Nodes[i];
Min=min(Min,Nodes[i]);
mark[i]=i;
}
for(int i=0;i<num_Arc;i++)
{
int uu,vv,temp_cost;
cin>>uu>>vv>>temp_cost;
Arc_vec[i].cost=2*temp_cost+Nodes[uu]+Nodes[vv];
Arc_vec[i].u=uu;
Arc_vec[i].v=vv;
}
cout<<Min+func(num_Node,num_Arc);
return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值