SCU 3369 Cheering up the Cows 最小生成树的遍历

题目描述:

Time Limit:3000ms Memory Limit:65536KB
Description
    Farmer John has grown so lazy that he no longer wants to continuemaintaining the cow paths that currently provide a way to visiteach of his N (5 <= N <= 10,000) pastures (conveniently numbered1..N). Each and every pasture is home to one cow. FJ plans to removeas many of the P (N-1 <= P <= 100,000) paths as possible while keepingthe pastures connected. You must determine which N-1 paths to keep.
    Bidirectional path j connects pastures S_j and E_j (1 <= S_j <= N;1 <= E_j <= N; S_j != E_j) and requires L_j (0 <= L_j <= 1,000) time
to traverse. No pair of pastures is directly connected by more thanone path.
    The cows are sad that their transportation system is being reduced.You must visit each cow at least once every day to cheer her up.Every time you visit pasture i (even if you're just travelingthrough), you must talk to the cow for time C_i (1 <= C_i <= 1,000).
    You will spend each night in the same pasture (which you will choose)until the cows have recovered from their sadness. You will end uptalking to the cow in the sleeping pasture at least in the morningwhen you wake up and in the evening after you have returned tosleep.
    Assuming that Farmer John follows your suggestions of which pathsto keep and you pick the optimal pasture to sleep in, determine theminimal amount of time it will take you to visit each cow at leastonce in a day.
    For your first 10 submissions, you will be provided with the results ofrunning your program on a part of the actual test data.
Input
* Line 1: Two space-separated integers: N and P
* Lines 2..N+1: Line i+1 contains a single integer: C_i

* Lines N+2..N+P+1: Line N+j+1 contains three space-separated
        integers: S_j, E_j, and L_j
Output
* Line 1: A single integer, the total time it takes to visit all the
        cows (including the two visits to the cow in your
        sleeping-pasture)
Sample Input
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
4 5 12
Sample Output
176
Hint
INPUT DETAILS:

              +-(15)-+
             /        \
            /          \
     1-(5)-2-(5)-3-(6)--5
            \   /(17)  /
         (12)\ /      /(12)
              4------+
              
OUTPUT DETAILS:

Keep these paths:

     1-(5)-2-(5)-3      5
            \          /
         (12)\        /(12)
             *4------+

Wake up in pasture 4 and visit pastures in the order 4, 5, 4, 2,
3, 2, 1, 2, 4 yielding a total time of 176 before going back to
sleep.

这道题目:意思就是求最小生成树中,从一个点出发,访问每一个点,回到原点的最短距离。因为生成树中没有环,从一个点到另一个点,不重复经过点只有唯一一条路径,如果想要做到访问每一个点并回到原点,就会两次经过生成树中每一条边。没有环,想要形成环路只能往回走。

这题在出发时和结束时都会经过起点,所以还要找一个耗时最小的 Ci , 作为刚开始的起点。

Kruskal 并查集

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std ;
int n , m , i , u , v , w ;
int C[10005] , father[10005] ;
struct Node{
	int u , v , w ;
	void Init( int _u , int _v , int _w ){
		u = _u , v = _v , w = _w ;
	}
	bool operator < ( const Node &b ) const {
		return w < b.w ;
	}
} Edge[100005] ;

int find( int u ){
	return u == father[u] ? u : father[u] = find( father[u] ) ;
}

int main(){
	while( ~scanf( "%d%d" , &n , &m ) ){
		for( i = 1 ; i <= n ; ++i ){
			scanf( "%d" , &C[i] ) ;
			father[i] = i ;
		}
		for( i = 1 ; i <= m ; ++i ){
			scanf( "%d%d%d" , &u , &v , &w ) ;
			Edge[i].Init( u , v , 2*w + C[u] + C[v] ) ;   // 每条边经过两次,端点的耗时都算上。
		}
		sort( Edge+1 , Edge+m+1 ) ;
		int cnt = 0 , one , two ;
		int ans = 0 ;
		for( i = 1 ; i <= m ; ++i ){
			one = find( Edge[i].u ) ;
			two = find( Edge[i].v ) ;
			if( one != two ){
				father[two] = one ;
				ans += Edge[i].w ;
				if( ++cnt == n-1 ) break ;
			}
		}
		printf( "%d\n" , ans+*min_element( C+1 , C+n+1 ) ) ;  // 加上最小的 C[i] 作为起点多余的一次耗时
	}
	return 0 ;
}

由于点比较多,用 Prim 在筛选上效率就低了 1556 ms , 还好限制是 3000ms , 一般还是写 Kruskal 

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std ;
#define INF 0x3f3f3f3f
typedef pair<int,int> P ;
int n , m , i , j , u , v , w ;
vector<P> One[10005] ;
int dis[10005] , C[10005] , book[10005] ;

void Prim(){
	memset( book , 0 , sizeof( book ) ) ;
	memset( dis , INF , sizeof( dis ) ) ;
	int ans = 0 , u , v , w , Min ;
	for( i = 0 ; i < (int)One[1].size() ; ++i )
		dis[One[1][i].first] = One[1][i].second ;
	dis[1] = 0 , book[1] = 1 ;
	for( i = 2 ; i <= n ; ++i ){
		Min = INF ;
		for( j = 2 ; j <= n ; ++j )
			if( !book[j] && dis[j] < Min )
				Min = dis[j] , u = j ;
		book[u] = 1 ;
		ans += Min ;
		for( j = 0 ; j < (int)One[u].size() ; ++j ){
			v = One[u][j].first , w = One[u][j].second ;
			if( !book[v] && dis[v] > w )
				dis[v] = w ;
		}
	}
	printf( "%d\n" , ans+*min_element( C+1 , C+n+1 ) ) ;
}

int main(){
	while( ~scanf( "%d%d" , &n , &m ) ){
		for( i = 1 ; i <= n ; ++i )
			scanf( "%d" , &C[i] ) , One[i].clear() ;
		for( i = 1 ; i <= m ; ++i ){
			scanf( "%d%d%d" , &u , &v , &w ) ;
			w = 2*w + C[u] + C[v] ;
			One[u].push_back( P( v , w ) ) ;
			One[v].push_back( P( u , w ) ) ;
		}
		Prim() ;
	}
	return 0 ;
}
顺便写了一个优先级队列,优化一下,可以到156ms
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std ;
#define INF 0x3f3f3f
typedef pair<int,int> P ;
int n , m , i , u , v , w ;
vector<P> One[10050] ;
int dis[10050] , C[10050] , book[10050] ;
struct Pair{
	int dis , u ;
	Pair(){}
	Pair( int _dis , int _u ){
		dis = _dis , u = _u ;
	}
} ;

class My_Priority_queue{// 优先级队列默认为小顶堆
private:
	int n ;             // 优先级队列当前的元素个数
	int Max_size ;
	Pair **a ;      // 数据指针, 顺序表
public:
	My_Priority_queue( int _Max_size ) ;              // 构造函数
	~My_Priority_queue() ;             // 析构函数
	void push( Pair *weight ) ;    // 往优先级队列中 push 数据
	void pop() ;                       // pop 栈顶
	Pair *top() ;                  // 访问栈顶元素
	bool empty() ;                     // 判断栈是否为空
	void Shift_down( int u ) ;         // 递归   堆向下调整
} ;

My_Priority_queue::My_Priority_queue( int _Max_size ){
	n = 0 ;
	Max_size = _Max_size ;
	a = new Pair*[3*Max_size] ;
	for( int i = 0 ; i < 3*Max_size ; ++i )
		a[i] = NULL ;
}

My_Priority_queue::~My_Priority_queue(){
	for( int i = 0 ; i < 3*Max_size ; ++i )
		a[i] = NULL ;
	if( a ) delete []  a ;
}

void My_Priority_queue::push( Pair *weight ){
	a[++n] = weight ;                    // push 压入的新元素放在末尾
	int parent = n >> 1 , k = n ;        // 因为前面的都是小顶堆
	while( parent ){               
		if( a[k]->dis < a[parent]->dis ) 
			swap( a[k] , a[parent] ) ;
		k = parent ;
		parent /= 2 ;                    // 从下向上调整, 只要和父亲相比
	}
}

void My_Priority_queue::pop(){
	if( empty() )  return ;
	swap( a[1] , a[n--] ) ;             // 把最后一个元素交换到堆顶, 堆大小 -1
	Shift_down( 1 ) ;                 // 从堆顶开始向下调整
}
 
Pair *My_Priority_queue::top(){     // 取堆顶元素 , 优先级队列中最小的元素
	return a[1] ;
}

bool My_Priority_queue::empty(){        // 判断优先级队列 ( 堆 ) 是否为空
	return n < 1 ;
}

void My_Priority_queue::Shift_down( int u ){
	int ans = u ;
	if( 2*u <= n && a[2*u]->dis < a[ans]->dis )            // 比较左孩子
		ans = 2*u ;
	if( 2*u+1 <= n && a[2*u+1]->dis < a[ans]->dis )        // 比较右孩子
		ans = 2*u+1 ;
	if( ans != u )                               // 最小的元素 a[ans] 在两个孩子当中
		swap( a[ans] , a[u] ) , Shift_down( ans ) ; // 交换 , 继续向下调整
}

void Prim(){
	memset( dis , INF , sizeof( dis ) ) ;
	memset( book , 0 , sizeof( book ) ) ;
	int ans = 0 , u , v , w ;
	My_Priority_queue Q( n ) ;
	Pair *cur = new Pair( dis[1] = 0 , 1 ) ;
	Q.push( cur ) ;
	while( !Q.empty() ){
		Pair *top = Q.top() ;
		Q.pop() ;
		u = top->u ;
		if( book[u] )
			continue ;
		book[u] = 1 ;
		ans += top->dis ;
		for( i = 0 ; i < (int)One[u].size() ; ++i ){
			v = One[u][i].first , w = One[u][i].second ;
			if( !book[v] && dis[v] > w ){
				Pair *t = new Pair( dis[v] = w , v ) ; 
				Q.push( t ) ;
			}
		}
	}
	printf( "%d\n" , ans+*min_element( C+1 , C+n+1 ) ) ;
}

int main(){
	while( ~scanf( "%d%d" , &n , &m ) ){
		for( i = 1 ; i <= n ; ++i )
			scanf( "%d" , &C[i] ) , One[i].clear() ;
		for( i = 1 ; i <= m ; ++i ){
			scanf( "%d%d%d" , &u , &v , &w ) ;
			w = 2*w + C[u] + C[v] ;
			One[u].push_back( P( v , w ) ) ;
			One[v].push_back( P( u , w ) ) ;
		}
		Prim() ;
	}
	return 0 ;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值