您的位置:首页 > 其它

【POJ】1639 Picnic Planning 度限制最小生成树

2014-07-18 14:42 387 查看
Picnic Planning

Time Limit: 5000MSMemory Limit: 10000K
Total Submissions: 9137Accepted: 3244
Description
The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of themselves into even the smallest vehicle. During the off-season,
the brothers like to get together for an Annual Contortionists Meeting at a local park. However, the brothers are not only tight with regard to cramped quarters, but with money as well, so they try to find the way to get everyone to the party which minimizes
the number of miles put on everyone's cars (thus saving gas, wear and tear, etc.). To this end they are willing to cram themselves into as few cars as necessary to minimize the total number of miles put on all their cars together. This often results in many
brothers driving to one brother's house, leaving all but one car there and piling into the remaining one. There is a constraint at the park, however: the parking lot at the picnic site can only hold a limited number of cars, so that must be factored into the
overall miserly calculation. Also, due to an entrance fee to the park, once any brother's car arrives at the park it is there to stay; he will not drop off his passengers and then leave to pick up other brothers. Now for your average circus clan, solving this
problem is a challenge, so it is left to you to write a program to solve their milage minimization problem.
Input
Input will consist of one problem instance. The first line will contain a single integer n indicating the number of highway connections between brothers or between brothers and the park. The next n
lines will contain one connection per line, of the form name1 name2 dist, where name1 and name2 are either the names of two brothers or the word Park and a brother's name (in either order), and dist is the integer distance between them. These roads will all
be 2-way roads, and dist will always be positive.The maximum number of brothers will be 20 and the maximumlength of any name will be 10 characters.Following these n lines will be one final line containing an integer s which specifies the number of cars which
can fit in the parking lot of the picnic site. You may assume that there is a path from every brother's house to the park and that a solution exists for each problem instance.
Output
Output should consist of one line of the form

Total miles driven: xxx

where xxx is the total number of miles driven by all the brothers' cars.
Sample Input
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3

Sample Output
Total miles driven: 183

Source
East Central North America 2000

传送门:【POJ】1639 Picnic Planning

题目分析:度限制最小生成树。

首先吐嘈一下。。。本题构思加查阅资料一共两天。。。然后写完以后直接1Y。。。有点惊讶。。。。

如果是所有度都是有限制的,那么这就是一个NP问题。。不过,本题十分特殊,只对一个顶点度限制,那么这题就可以贪心的解决了。

首先,我们可以将度限制的顶点V0从原图中删去(当然不是真的删去啦,就是和它相连的边都移走),然后对各个连通块求最小生成树(如果用kruskal的话,一次就可以求出所有连通块的最小生成树了)。

计连通块的数量为m,度限制为deg,如果m>deg,那么肯定不可能有符合条件的生成树了。

现在我们假设m<=deg。

首先,将所有连通块连接到V0上,取每个连通块到V0上花费最小的边即可。这样我们就得到了m度生成树。

怎么样从m度生成树伸展到m+1度生成树?我们可以枚举所有现在不与V0直接相连的点x,但是存在V0和x直接相连的边,设边权为cost,那么V0和x相连后应该删除的边应该是两点相连后对应连通块内所形成的环中的最大的边,设权值为val,那么应该删除的边是枚举所有点x后,cost-val最小的那个点 x',然后用(V0,x')替换掉这条边。这样我们就从m度生成树伸展到了m+1度生成树了~。

但是直接枚举时间花销太大,因此我们可以dfs预处理出与每个x相连时应该删除的边的权值maxcost以及编号flag。以每个与V0相连的顶点作为根扩展出dfs树即可。具体见代码。maxcost[v] = max { maxcost[u] , w ( u , v ) },u是v的父亲。flag[v] = ( maxcost[v] == maxcost[u] ? flag[u] : i ),i是边( u , v )的编号。

这样就可以快速从m度生成树伸展到m+1生成树了~

然后当m == deg 的时候就可以停止伸展了,答案即所有度生成树中的最小值。

代码如下:

#include <cstdio>
#include <map>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REPV( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define fi first
#define se second
#define ms( a , x ) memset ( a , x , sizeof a )

const int MAXN = 21 ;
const int MAXE = 1000000 ;
const int INF = 0x3f3f3f3f ;

struct Edge {
	int v , c , f , n ;
	Edge () {}
	Edge ( int var , int cost , int flag , int next ) :
		v ( var ) , c ( cost ) , f ( flag ) , n ( next ) {}
} ;

struct Line {
	int x , y , c ;
	Line () {}
	Line ( int X , int Y , int C ) :
		x ( X ) , y ( Y ) , c ( C ) {}
	bool operator < ( const Line &a ) const {
		return c < a.c ;
	}
} ;

struct MST {
	Edge edge[MAXE << 1] ;
	int adj[MAXN] , cntE ;
	Line line[MAXE] ;//不与根直接相连的边
	Line vline[MAXE] ;//与根直接相连的边
	int cntv , cntm ;//cntv:与根直接相连的边的数量,cntm:不与根直接相连的边的数量
	map < string , int > mp ;//字符串映射整型
	int vis[MAXN] ;//vis[i] = 1 表示与根直接相连,0 则反之
	int p[MAXN] ;//并查集parent
	
	//kruskal后第一次建树
	int link_cost[MAXN] ;//建树时每个连通块到根结点的最小花费
	int degv[MAXN] ;//对应建树时链接的节点编号
	
	//dfs中记录的信息
	int flag[MAXN] ;//记录根与每个节点相连对应要删除的边的编号
	int maxcost[MAXN] ;//记录根每个节点相连形成的环上的边权最大的边(即要删除的边),边界条件是与根直接相连的边权为-INF
	
	int n , m , deg_limit ;//n:点数,m:边数,deg_limit:度限制
	
	void init () {
		n = 0 ;
		cntm = 0 ;
		cntv = 0 ;
		cntE = 0 ;
		ms ( vis , 0 ) ;
		mp.clear () ;
		REP ( i , MAXN )
			p[i] = i ;
		ms ( adj , -1 ) ;
	}
	
	void addedge ( int u , int v , int c ) {
		edge[cntE] = Edge ( v , c , 1 , adj[u] ) ;
		adj[u] = cntE ++ ;
		edge[cntE] = Edge ( u , c , 1 , adj[v] ) ;
		adj[v] = cntE ++ ;
	}
	
	int get () {
		char s[MAXN] ;
		scanf ( "%s" , s ) ;
		map < string , int > :: iterator it = mp.find ( s ) ;
		if ( it != mp.end () )
			return it -> se ;
		else {
			mp.insert ( map < string , int > :: value_type ( s , n ) ) ;
			return n ++ ;
		}
	}
	
	int find ( int x ) {
		return p[x] != x ? ( p[x] = find ( p[x] ) ) : x ;
	}
	
	void input () {
		mp["Park"] = n ++ ;//Park is n zero
		int x , y , c ;
		REP ( i , m ) {
			x = get () , y = get () ;
			scanf ( "%d" , &c ) ;
			if ( x != 0 && y != 0 )
				 line[cntm ++] = Line ( x , y , c ) ;
			else
				vline[cntv ++] = Line ( 0 , x ? x : y , c ) ;
		}
		m = cntm ;
		scanf ( "%d" , °_limit ) ;
	}
	
	int kruskal () {
		sort ( line , line + m ) ;
		int ans = 0 ;
		REP ( i , m ) {
			int x = find ( line[i].x ) ;
			int y = find ( line[i].y ) ;
			if ( x != y ) {
				ans += line[i].c ;
				p[x] = y ;
				addedge ( line[i].x , line[i].y , line[i].c ) ;
			}
		}
		return ans ;
	}
	
	int build_m_limit_tree () {
		sort ( vline , vline + cntv ) ;
		ms ( link_cost , INF ) ;
		ms ( degv , 0 ) ;
		REP ( i , cntv ) {
			int pos = p[vline[i].y] ;//得到每个点所属的连通块
			if ( link_cost[pos] > vline[i].c ) {
				degv[pos] = vline[i].y ;//每个连通块与根相连的节点编号
				link_cost[pos] = vline[i].c ;//连接的花费
			}
		}
		int ans = 0 ;
		REPF ( i , 1 , n - 1 )
			if ( degv[i] ) {
				//addedge ( 0 , degv[i] , link_cost[i] ) ; 不需要直接添加
				ans += link_cost[i] ;
				vis[degv[i]] = 1 ;
			}
		return ans ;
	}
	
	void dfs ( int u , int fa ) {
		for ( int i = adj[u] ; ~i ; i = edge[i].n ) {
			int v = edge[i].v ;
			if ( v == fa || edge[i].f == 0 )//后继是父亲或者该边已经被删除
				continue ;
			if ( maxcost[u] > edge[i].c ) {//前面的边比现在的大
				maxcost[v] = maxcost[u] ;
				flag[v] = flag[u] ;
			}
			else {//当前边最大
				maxcost[v] = edge[i].c ;
				flag[v] = i ;
			}
			dfs ( v , u ) ;
		}
	}
			
	
	void solve () {
		init () ;
		input () ;
		int ans = kruskal () ;
		int deg = 0 ;
		
		REPF ( i , 1 , n - 1 ) {
			find ( i ) ;
			if ( p[i] == i )
				++ deg ;//求得连通块个数
		}
		
		if ( deg_limit < deg ) {//删除根结点形成的连通块个数大于度限制,肯定得不到生成树
			printf ( "can not be a tree!!!\n" ) ;
			return ;
		}
		
		ans += build_m_limit_tree () ;//第一次建树,从根连接到每个连通块
		
		while ( deg < deg_limit ) {//不断循环从m度生成树生长成m+1度生成树
			ms ( maxcost , -INF ) ;
			int tmp_min = INF , tmpi ;
			REPF ( i , 1 , n - 1 ) {//预处理出与每个点相连时应该删除的边以及要连接的点
				if ( vis[i] ) {
					dfs ( i , 0 ) ;
				}
			}
			REP ( i , cntv ) {
				int pos = vline[i].y ;
				if ( !vis[pos] )//枚举所有还不与根直接相连的点
					if ( tmp_min > vline[i].c - maxcost[pos] ) {
						//选取连接后tmp_min最小的点,tmp_min即添加一条边再删除一条边的花费
						tmp_min = vline[i].c - maxcost[pos] , tmpi = pos ;
					}
			}
			vis[tmpi] = 1 ;//与根连接
			//addedge ( 0 , tmpi , vline[tmpi] ) ; 不需要直接添加
			edge[flag[tmpi]].f = edge[flag[tmpi] ^ 1].f = 0 ;//删除边
			if ( tmp_min >= 0 )//如果已经不能再减小,易得以后也不会再减小,直接跳出循环
				break ;
			ans += tmp_min ;
			++ deg ;//度+1
		}
		printf ( "Total miles driven: %d\n" , ans ) ;
	}
} ;

MST z ;

int main () {
	while ( ~scanf ( "%d" , &z.m ) )
		z.solve () ;
	return 0 ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: