HDU 1055 ---给树着色

Color a Tree

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 983    Accepted Submission(s): 319


Problem Description
Bob is very interested in the data structure of a tree. A tree is a directed graph in which a special node is singled out, called the "root" of the tree, and there is a unique path from the root to each of the other nodes.

Bob intends to color all the nodes of a tree with a pen. A tree has N nodes, these nodes are numbered 1, 2, ..., N. Suppose coloring a node takes 1 unit of time, and after finishing coloring one node, he is allowed to color another. Additionally, he is allowed to color a node only when its father node has been colored. Obviously, Bob is only allowed to color the root in the first try.

Each node has a “coloring cost factor”, Ci. The coloring cost of each node depends both on Ci and the time at which Bob finishes the coloring of this node. At the beginning, the time is set to 0. If the finishing time of coloring node i is Fi, then the coloring cost of node i is Ci * Fi.

For example, a tree with five nodes is shown in Figure-1. The coloring cost factors of each node are 1, 2, 1, 2 and 4. Bob can color the tree in the order 1, 3, 5, 2, 4, with the minimum total coloring cost of 33.



Given a tree and the coloring cost factor of each node, please help Bob to find the minimum possible total coloring cost for coloring all the nodes.
 

 

Input
The input consists of several test cases. The first line of each case contains two integers N and R (1 <= N <= 1000, 1 <= R <= N), where N is the number of nodes in the tree and R is the node number of the root node. The second line contains N integers, the i-th of which is Ci (1 <= Ci <= 500), the coloring cost factor of node i. Each of the next N-1 lines contains two space-separated node numbers V1 and V2, which are the endpoints of an edge in the tree, denoting that V1 is the father node of V2. No edge will be listed twice, and all edges will be listed.

A test case of N = 0 and R = 0 indicates the end of input, and should not be processed.
 

 

Output
For each test case, output a line containing the minimum total coloring cost required for Bob to color all the nodes.
 
 
做这题时,首先想到的是用并查集的思想来做,但是做了之后才发现不合适,原因如下:
(1) 当使用sort函数之后,原有的顺序就已经被打乱了,就不能正确找到某节点的父亲,在并查集中,没有改变节点的顺序;
(2) 如果想使用并查集,则节点原来的顺序一定不能改变,(可以每次找到当前权值最大的节点,但是这样就会在查找上面花费大量的时间,不知道会不会超时)
先把做到一半的错误代码贴下:
#include<iostream>
#include<algorithm>
using namespace std;
typedef struct node
{
	int num;
	int father;
	int cost;
	bool visit;
}Node;
Node Nd[1002];
int total,tim;//total记录总花费;tim记录时间
void UnionSet(int a,int b)   //构成有向图
{
	Nd[b].father=a;
}
void proce(int i)
{
	if(Nd[i].visit) return ;
	if(Nd[i].num==Nd[i].father)      //如果当前节点是根节点
	{
		total+=Nd[i].cost*tim;  //对该节点着色,所有花费的总和
		tim++;             //时间+1
		Nd[i].visit=true;  //标记该节点已经着色
		return ;
	}
	else   //如果该节点没有被处理,且不是根节点
	{
		proce(Nd[i].father);//先处理其父节点
		total+=Nd[i].cost*tim;
		tim++;
		Nd[i].visit=true;
	}
}
bool cmp(Node x,Node y)
{
	return x.cost>y.cost;
}
int main()
{
	int n,m;
	while((cin>>n>>m)&& n && m )
	{
		for(int i=1;i<=n;i++)   //节点初始化
		{
			cin>>Nd[i].cost;
			Nd[i].father=i;
			Nd[i].num=i;   //编号
			Nd[i].visit=false;
		}
		int x,y;
		for(int i=1;i<n;i++)   //构造树,
		{
			cin>>x>>y;
			UnionSet(x,y);
		}
		sort(Nd+1,Nd+n+1,cmp);
		for(int i=1;i<=n;i++)
			cout<<" ( "<<Nd[i].cost<<" , "<<Nd[i].num<<" , "<<Nd[i].father<<" , "<<Nd[i].visit<<" ) "<<endl;
		total=0;tim=1;
		cin>>total;
		for(int i=1;i<=n;i++)
		{
			proce(i);
		}
		cout<<total<<endl;
	}
	return 0;
}

  

这题跟HDU 3979(打怪兽有联系,又有区别)

联系:都是使用贪心算法,按某一值排序后(降序),先处理最大值,直到结束

区别:这里是一棵树,节点与节点之间有联系(父亲---儿子),但是怪兽间是没有关系的;

起初看到树,就想到之前的并查集,但是后来发现行不通,原因是,使用并查集好像不能随便改变元素间的位置吧,不然怎么知道之前它的位置在哪里呢?这样就会导致之前记录的关系没有用了,纠结中~~~

 

 

看了别人的解题报告,终于弄懂了点:

首先:  使用了并查集的思想,(注只利用了“并”思想);

其次: 没有想象中的那么简单(只考虑val的值)

        正解:time-----记录完全解决该节点需要花费的时间

                val-------该节点所对应的权值

                cost------解决该节点所需要的总花费

              所以需要比较的是 a[idx].val*a[i].time<a[i].val*a[idx].time

在将当前节点与父节点合并时,注意点有:

(1)更新的 cost = 父亲的cost + 儿子的cost + 儿子的val * 父亲的time(最后一项是由于要先处理父亲才能处理儿子,所以儿子必须等待“父亲的time”这么多时间,这段时间产生的cost即为 “儿子的val * 父亲的time” );

(2)更新的 time = 父亲的time + 儿子的time

(3) 更新的val = 父亲的val + 儿子的val

(4) 将儿子的节点清空,即这个节点已经不用再次处理了,

在这里,刚开始有疑惑,我就想,如果这样的话,那这个节点被他的父亲包括了,那他的儿子怎么办呢?

后来想了下,明白了,如果这样的话,当这个节点被选中,而他的父亲的代价是0,同时,再一次将这个节点合并到他的父亲节点中,导致父亲节点被更新(非0了),而这个节点被清空,(相当于把这个节点向前移,自己觉得有点浪费时间,但是思路清晰简单)

 

总结:本人觉得在比较那里有值得学习的地方:

(1)

			int idx=0;
			for(int i=1;i<=n;i++)
			{
				if((i!=r)&&(a[i].time)&&((idx==0)||(a[idx].val*a[i].time<a[i].val*a[idx].time)))
					idx=i;
			}

  这里很经典,首先对idx赋初值0;如果非根节点,且未被访问过,并且这是第一个(如果只有一个元素,就无法比较-----很好的思路)或者(不是第一个,就必须进行比较,找到满足要求的)满足比较条件;

(2)

 就是将子节点与父节点合并的思路,感觉有点像DP,但又感觉不是太像;还是贪心的思想比较明确,每次寻找的是权值最大的(这里还跟时间有关,注意!)

 

 

正确的代码如下:

#include<iostream>
#include<stdio.h>
using namespace std;
int father[1002];
typedef struct node
{
	int time;
	int val;
	int cost;
	void clear()
	{
		time=val=cost=0;
	}
}Node;
Node a[1002];
int main()
{
	int n,r;
	while((cin>>n>>r)&&n&&r)
	{
		for(int i=1;i<=n;i++)
		{
			cin>>a[i].val;
			a[i].cost=a[i].val;
			a[i].time=1;
		}
		int x,y;
		for(int i=1;i<n;i++)
		{
			cin>>x>>y;
			father[y]=x;
		}
		while(true)
		{
			int idx=0;
			for(int i=1;i<=n;i++)
			{
				if((i!=r)&&(a[i].time)&&((idx==0)||(a[idx].val*a[i].time<a[i].val*a[idx].time)))
					idx=i;
			}
			if(idx==0) break;
			int f=father[idx];
			a[f].cost+=a[idx].cost+a[idx].val*a[f].time;
			a[f].val+=a[idx].val;
			a[f].time+=a[idx].time;      //将当前节点和它的父节点合并
			a[idx].clear();              //清空
		}
		cout<<a[r].cost<<endl;
	}
	return 0;
}

  

posted @ 2014-05-07 14:19  zhoudan  阅读(166)  评论(0)    收藏  举报