微软hiho上的笔试题:Tower Defense Game

题目链接:http://hihocoder.com/contest/mstest2015sept1/problem/3

题目:

题目3 : Tower Defense Game

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

There is a tower defense game with n levels(missions). The n levels form a tree whose root is level 1.

In the i-th level, you have to spend pi units of money buying towers and after the level, you can sell the towers so that you have qi units of money back.

微软hiho上的笔试题:Tower Defense Game_第1张图片

Each level is played once. You can choose any order to complete all the levels, but the order should follow the following rules:

1: A level can be played when all its ancestors are completed.

2: The levels which form a subtree of the original tree should be completed consecutively in your order.

You want to know in which order of completion you can bring the minimum money before starting the first level.

输入

The first line contains one single integers, n - the number of levels. (1<=n<=10000)

The next n lines describes the levels. Each line contains 2 integers, pi and qi which are described in the statement. (0<=qi<=pi<=20000)

The next n-1 lines describes the tree. Each line contains 2 integers, ai and bi which means there is an edge between level ai and level bi.

For 30% of the data, n<=100.

For 60% of the data, n<=1000.

输出

Print one line with an single integer representing the minimum cost.

样例提示

There are two orders of completing all levels which are: 1234 and 1342.

In the order 1234, the minimum beginning money is 5.

In the order 1342, the minimum beginning money is 7.

1324 is not a valid order because level 3 and level 4 are not completed consecutively.

样例输入
4
2 1
4 3
2 1
2 1
1 2
1 3
3 4
样例输出
5
分析:

题目的意思应该是每个节点都有买入的价格和卖出的价格,让你选择一条遍历路径,可以使你刚开始需要带的钱最少,从左边开始走完全部节点需要至少5元,从右边开始走完成全部节点至少需要7元。所以选择左边,而且,这里说到了:The levels which form a subtree of the original tree should be completed consecutively in your order.也就是说,如果你开始一棵子数,你必须完成它,题目你开始了3这个子树,你必须要走完,就是说要3和4都要走完才能走2。 而不能1-3-2-4这样子。

那么我们的思路是如何呢?

注意到以下三种情况(题目中为情况1),你就知道问题有多复杂了:

微软hiho上的笔试题:Tower Defense Game_第2张图片

(情况1图)

微软hiho上的笔试题:Tower Defense Game_第3张图片

 (情况2图)

这里,对于节点1来说,max的结果是100。

微软hiho上的笔试题:Tower Defense Game_第4张图片

 (情况3图)

而这里,对于节点1来说max是101。

所以,这里我们有个印象,就是对于节点三元组来说,max是和根节点的支出有关系,也和左右节点的支出收入有关,还和左右子数的遍历顺序有关(题目中的例子已经能够说明)。

因此,我们就先对三元组进行分析:

微软hiho上的笔试题:Tower Defense Game_第5张图片

在三元组这样的结构中,假设根节点的支出和收入是p1和q1,左结点的支出和收入是p2和q2,右结点的支出和收入是p3和q3,那么,

我们先来确定一下路径,是先走左边节点还是先走右边节点:

那么我们就来分析一下:max可能出现的几个点:

如果走左边:那么max的可能值是:

(左1)买了1之后的p1

(左2)买了2之后的p1-q1+p2

(左3)买了3之后的p1-q1+p2-q2+p3

如果走右边,那么max的可能值是:

(右1)买了1之后的p1

(右2)买了3之后的p1-q1+p3

(右3)买了2之后的p1-q1+p3-q3+p2

注意到题目中要求最小值其实就是求它们这几个步骤中最大值的最小值,

假设买入价都高于卖出价,那么这里我们可以得出左3>右2,右3>左2。所以主要就是左3和右3的比较。

如果左3<右3,即p2-q2+p3<p3-q3+p2  =>   q3<q2,就选择左边的路,理解为左右两边都支出为p2 + p3,但是左边q2收入多,所以总的付出就少了。

如果左3>右3,即p2-q2+p3>p3-q3+p2 => q2<q3,就选择右边的路。

结论:对于选择左边还是右边,假定总付出psum是一定的,但是需要带出去的钱是psum - q左/右,也就是psum-qsum+q右/左,所以哪边的q大,就选择先从哪边走;哪边的q小,哪边就最后走。

我们可以把这个结论扩展一下,就可以适用于多叉树的情况:

微软hiho上的笔试题:Tower Defense Game_第6张图片

把各个节点按照qi的递减顺序排列,q越大的越早遍历,q最小的最后遍历,这样得到的max(需要带的钱)会是最小的

可以看下面的例子帮助理解:

微软hiho上的笔试题:Tower Defense Game_第7张图片

(情况4图)

那么,我们再把问题扩展一下,如果子节点的p不一定都大于q呢,也就是说可能出现收入大于支出的情况呢?

我们应该首先要先走这些分支,赚到钱再说(因为先走其他的路肯定会越欠越多)

而且要先走q>p中p小的,因为这样投入小而一定会有收益。

详细请看下图:

微软hiho上的笔试题:Tower Defense Game_第8张图片

也举一个例子吧:(这里为了简化,只列出 一层孩子节点。)

微软hiho上的笔试题:Tower Defense Game_第9张图片

(情况5图)(还未排序,如果排序应该是6-5-2-3-4)

至此,我们已经知道了如何选择路径的顺序。而把节点统一起来,就需要递归来探索。

这里我们选择DFS,并返回一个result的结构体,定义如下:

struct result{
	int p;//p为支出
	int q;//q为收入
	result(int pp, int qq) :p(pp), q(qq){}
};
而DFS定义如下:

result DFS(int root){
	if (当前节点是叶子节点){
		return result(当前节点的支出,当前节点的收入);
	}
	else{//当前节点不是叶子节点,有孩子
		DFS(各个孩子节点编号);//循环,获得每个孩子的p和q
		把孩子节点按照q > p与否分成两组,其中q > p一组按照q递增排序,p > q一组按照q递减排序;
		然后模拟遍历过程,得到max,即需要的钱max,也即当前节点需要返回的父p;
		父q = 父p - psum + qsum;
		return result(父p, 父q);
	}
}


代码:

代码先贴上二叉树和p>q的部分吧,困了。剩下的再补上。

以下是只考虑二叉树和p>q的情况,而考虑全部情况代码的更简洁,大家可以直接看第二部分的代码。

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
using namespace std;
struct Cost{
	int in;//in为支出,买入的钱
	int out;//out为收入,卖出的钱
}cost[10001];//存储每个节点买入卖出的价格
struct Node{
	int lchild = -1;
	int rchild = -1;
}node[10001];//存储每个节点的孩子节点,这里假设题目中都是二叉树


struct result{
	int p;//p为支出
	int q;//q为收入
	result(int pp, int qq) :p(pp), q(qq){}
};
result DFS(int root){
	if (node[root].lchild == -1 && node[root].rchild == -1){//no child
		return result(cost[root].in, cost[root].out);//支出就是in,收入就是out
	}
	else if (node[root].lchild != -1 && node[root].rchild == -1){//only lchild
		result lhs_res = DFS(node[root].lchild);
		int p2 = lhs_res.p;
		int q2 = lhs_res.q;
		int p1 = max(cost[root].in, cost[root].in - cost[root].out + p2);//支出为当前结点或当前结点支出+左结点支出的最大值
		int q1 = p1 - (cost[root].in - cost[root].out + p2 - q2);//收入为支出-亏损的钱
		return result(p1, q1);
	}
	else{
		result lhs_res = DFS(node[root].lchild);
		result rhs_res = DFS(node[root].rchild);
		int p2 = lhs_res.p;//左结点的支出
		int q2 = lhs_res.q;//左结点的收入
		int p3 = rhs_res.p;//右结点的支出
		int q3 = rhs_res.q;//右结点的收入
		if (q3 < q2){//choose left first先从左边走,选择最少中需要带的最多的钱
			int p1 = max(cost[root].in, cost[root].in - cost[root].out + p2);//支出为(1)当前结点(2)当前结点剩余+左结点支出
			p1 = max(p1, cost[root].in - cost[root].out + p2 - q2 + p3);//(3)当前结点花费+左结点花费+右结点支出的最大值
			int q1 = p1 - (cost[root].in - cost[root].out + p2 - q2 + p3 - q3);//收入为支出-亏损的钱
			return result(p1, q1);
		}
		else{//choose right first先从右边走,选择最少中的需要带的最多的钱
			int p1 = max(cost[root].in, cost[root].in - cost[root].out + p3);//支出为(1)当前结点(2)当前结点剩余+右结点支出
			p1 = max(p1, cost[root].in - cost[root].out + p3 - q3 + p2);//(3)当前结点花费+右结点花费+左结点支出的最大值
			int q1 = p1 - (cost[root].in - cost[root].out + p3 - q3 + p2 - q2);//收入为支出-亏损的钱
		return result(p1, q1);
		}
	}
}


int main(){
	//freopen("F://Temp/input.txt", "r", stdin);
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i){
		cin >> cost[i].in >> cost[i].out;
	}
	int p, c;
	for (int i = 0; i < n - 1; ++i){
		cin >> p >> c;
		if (node[p].lchild == -1){//孩子按先填满左孩子再右孩子
			node[p].lchild = c;
			
		}
		else node[p].rchild = c;
	}
	result res = DFS(1);//题目说了,1是根节点,不然我们可以在Node结构体中设立一个parent找根节点
	cout << res.p << endl;//根节点的支出,也就是最少需要带的钱
	return 0;
}


那么,如果要针对的是多叉树,就要改变一下Node结构体,把孩子节点变为vector<int>children的形式,如下

struct Node{
	vector<int>children;//多叉树的情况
}node[10001];//存储每个节点的孩子节点,这里假设题目中都是二叉树
而且还要有一个比较函数,用来判断多个孩子节点的路径顺序:

int isGain(int C){//判断买卖当前节点的营收状态
	if (cost[C].in - cost[C].out > 0) return -1;//-1表示亏钱
	else if (cost[C].in == cost[C].out) return 0;//0表示刚好平衡
	else return 1;//表示赚钱
}

bool cmp(int A, int B){
	int AA, BB;//表示A和B是否是赚钱的标志
	AA = isGain(A);
	BB = isGain(B);
	if (AA != BB)return AA > BB;//赚钱的肯定要排,然后再是平衡的,然后再是亏钱的
	else{//A,B都是相同性质的
		if (AA == 1) return cost[A].in < cost[B].in; //赚钱时,要把pi小的排前面,前面有过讨论
		else if (AA == 0) return cost[A].in < cost[B].in; //其实平衡时顺序无所谓,不过也先定义一个,把小的排前面
		else if (AA == -1) return cost[A].out > cost[B].out; //亏钱时要把回报qi大的排前面
	}
}
所以,代码也就出炉了:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
using namespace std;

struct Cost{
	int in;//in为支出,买入的钱
	int out;//out为收入,卖出的钱
}cost[10001];//存储每个节点买入卖出的价格
struct Node{
	vector<int>children;//多叉树的情况
}node[10001];//存储每个节点的孩子节点,这里假设题目中都是二叉树

struct result{
	int p;//p为支出
	int q;//q为收入
	result(int pp, int qq) :p(pp), q(qq){}
};

int isGain(int C){//判断买卖当前节点的营收状态
	if (cost[C].in - cost[C].out > 0) return -1;//-1表示亏钱
	else if (cost[C].in == cost[C].out) return 0;//0表示刚好平衡
	else return 1;//表示赚钱
}

bool cmp(int A, int B){
	int AA, BB;//表示A和B是否是赚钱的标志
	AA = isGain(A);
	BB = isGain(B);
	if (AA != BB)return AA > BB;//赚钱的肯定要排,然后再是平衡的,然后再是亏钱的
	else{//A,B都是相同性质的
		if (AA == 1) return cost[A].in < cost[B].in; //赚钱时,要把pi小的排前面,前面有过讨论
		else if (AA == 0) return cost[A].in < cost[B].in; //其实平衡时顺序无所谓,不过也先定义一个,把小的排前面
		else if (AA == -1) return cost[A].out > cost[B].out; //亏钱时要把回报qi大的排前面
	}
}

result DFS(int root){
	int p1 = cost[root].in, q1 = 0;//要返回的总支出和总收益
	int p1_tmp = cost[root].in - cost[root].out;//模拟过程中暂存p1的过程量,寻找最大值,赋初值为根节点的支出-收入
	vector<int>V = node[root].children;
	for (int i = 0; i < V.size(); ++i){//遍历每个孩子节点,*point
		result res = DFS(V[i]);//递归找出每个孩子节点的支出和收入
		cost[V[i]].in = res.p;//更新为它们的in和out
		cost[V[i]].out = res.q;
	}
	sort(V.begin(), V.end(), cmp);
	for (int i = 0; i < V.size(); ++i){
		p1_tmp += cost[V[i]].in;//买入新的结点
		if (p1_tmp > p1)p1 = p1_tmp;//更新p1(max)
		p1_tmp -= cost[V[i]].out;//卖出当前节点
	}//过程结束后可以看到p1_tmp就是所有节点支出收入差值的总和
	q1 = p1 - p1_tmp;//可算出总收益,即需要花费的最大支出-买卖过程中亏损的钱
	return result(p1, q1);
}

int main(){
	//freopen("F://Temp/input.txt", "r", stdin);
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i){
		cin >> cost[i].in >> cost[i].out;
	}
	int parent, child;
	for (int i = 0; i < n - 1; ++i){
		cin >> parent >> child;
		node[parent].children.push_back(child);
	}
	result res = DFS(1);//题目说了,1是根节点,不然我们可以在Node结构体中设立一个parent找根节点
	cout << res.p << endl;//根节点的支出,也就是最少需要带的钱
	return 0;
}

结果:

情况1对应输入和输出:

微软hiho上的笔试题:Tower Defense Game_第10张图片


情况2对应的输入和输出:

微软hiho上的笔试题:Tower Defense Game_第11张图片

情况3对应的输入和输出:

微软hiho上的笔试题:Tower Defense Game_第12张图片

情况4对应的输入和输出:

微软hiho上的笔试题:Tower Defense Game_第13张图片

情况5对应的输入和输出:

微软hiho上的笔试题:Tower Defense Game_第14张图片



——Apie陈小旭


你可能感兴趣的:(二叉树,DFS,hiho)