ACM解题总结——HihoCoder1199 (微软笔试题)

来源:互联网 发布:怎么解决淘宝自主访问 编辑:程序博客网 时间:2024/05/21 11:19
题目来源:
    HihoCoder1199 

题目要求:
    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.


    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.

题目大意
    游戏包含N个关卡,这些关卡的关系可以用一个树形结构来描述,进入每个关卡i需要金钱pi,完成该关卡后可以获得回报qi。游戏中可以任意选择关卡来完成,但需要满足如下两个条件:
    (1) 对于每一个关卡,只有它的父节点代表的关卡已经完成,该关卡才可以进行。
    (2) 如果有多个关卡组成了一棵子树,则这些关卡必须以连续的顺序完成(即如果遍历到某一节点,那么接下来就必须遍历以它为根的子树的所有节点。
    题目要求通过合理地安排关卡通过的顺序,计算出进入整个游戏前,需要携带的最少数目的钱数。

解答:
    
本题中关卡完成的过程其实就是树的遍历过程,由于子节点访问前要求父节点已经完成访问,因此,这里的遍历过程是前序遍历。但遍历根节点后,对于子树的遍历顺序却不确定。这里不同的子树遍历顺序就会得到不同的“初始资金”(进入游戏前需要携带的钱数)的结果。本题的关键在于如何找到使得初始资金达到最小值的子树遍历顺序。

·初始资金的计算:
    对于游戏中的“初始资金”,可以采用下面的方式计算。对于给定的一个关卡的序列:s1, s2, ... sn,如果一次遍历序列中的每一个关卡,则我们可以用下面的过程来计算从s1遍历至si时需要的所有花费:首先遍历s1,需要资金p1,当关卡s1完成后,可以得到q1的资金;此时遍历s2,需要p2资金,而此时已有q1的资金,因此还需要p2-q1的资金,关卡s2完成后,可以得到q2资金,然后遍历s3.....以此类推,遍历到si后,就拥有qi-1的资金,此时还需要补充pi - qi-1资金。因此如果用Wi表示从关卡s1遍历至关卡si时,在不包括关卡si的回报时,所需要投入的总的资金数,那么:


    这里的计算结果Wi,是完成关卡s1si实际所需要的资金数目。此时,我们可以讨论一下对于不同的i值,Wi的大小关系。对于Wi我们总可以将Wi写成下面的形式:
 
    显然,如果pi>qi-1Wi就大于Wi-1,否则Wi就比Wi-1小。出现这种现象的原因是:每一个关卡的实际消耗资金可能会比其初始投入资金要少,因此,完成关卡时,投入的资金可能要比关卡实际消耗的资金要多,因此对于WiWi-1小的情况:如果初始资金为Wi,遍历关卡s1si-1的过程中一定存在“赊欠”的情况,即在完成某个关卡前就已经取用了当前关卡完成后的回报资金。
    对于序列:s1, s2, .... sn,我们可以取i= 1, 2, ... n,就可以得到N个不同的Wi值,而完成序列中所有的关卡所需要的初始投入资金,就是这些Wi值的最大值,即:
 
    对于有N个关卡的序列,共有N!个不同的排列方式,如果将这些排列分别记作:a1, a2, ...aN!,那么题目中的答案就是这N!种排列中计算的初始投入资金的最小值,如果将其记作W,那么:
 
    接下来的问题是如何确定到底哪个排列结果才会得到最小的W值。

·只有两个子树的情况:
    首先我们考虑一种简单的情况,即:对于每个节点,它至多有两个子树,这里可以将它们分别称为左子树和右子树。由于在遍历子树时必须先遍历根节点,所以,这里共有两种遍历方式:根->左->右,或者:根->右->左。下面分别考虑这个两种情况,记:根节点、左子树节点、右子树节点分别为节点1、2、3,如下图:

    对于:根->左->右 的遍历顺序,我们可以计算经过每个节点时,所需要的最小的资金数,如下:
    

    对于:根->右->左 的顺序也可以采用同样的处理方式:

    此时,最小的初始金额为:
     
    这里,我们可以这样的现象:L1=R1L3≥R2 并且 R3≥L2然后,分两种情况考虑:
    ① q3 > q2: 此时:R3 < L3。由于 Max{L1,L2,L3}≥L3,因此:Max{L1, L2, L3}≥L3;又因为:L3≥R2,因此:Max{L1, L2, L3}≥R2;又:Max{L1, L2, L3}≥L1 = R1,所以:Max{L1, L2, L3}≥R1,因此,我们可以得到这样的结论:

此时:

    ② q2 > q3: 此时,和情况①类似,我们可以得到下面的结论:

对于以上两种情况,我们可以发现:产生最终结果的遍历顺序一定是先访问q值大的子树,然后再访问q值小的子树,因此我们可以得到结论: 
    对于只有两棵子树的情况,子树的遍历以q值递减的顺序时,可以得到最优解。

·更一般的情况:
    下面将前文中的结论推广到更一般的情况。前面已经说明,对于有N个节点的一个排列:S1, S2, ... Sn,它的初始投入资金计算方式为:
     
    这里我们从序列中找到这样的两个相邻的元素SiSi+1使得:qi<qi+1, 并且将上面式子中的每一项都展开,如下:

     
    然后,我们将si和si+1的位置交换,上面的式子就变为:
    
通过观察上面的两个式子,可以发现这样的特点,对于W和W'的计算式子中的每一项Wk,可以发现:
    ① 1≤k≤i-1时:Wk = W'k
    ② k=i 或 k=i+1时:有:Wk+1≥W'k 并且 W'k+1≥Wk;又因为:qi+1>qi,所以还有:Wk+1>W'k+1
    ③ k>i+1时:Wk = W'k
于是,我们可以得到:
     
于是,我们发现:通过交换SiSi+1,最初的投入资金就会减少。所以,下面的结论是成立的:
    对于序列S1, S2, ..., Sn,如果存在两个相邻的元素:Si和Si+1满足:qi<qi+1,那么交换Si和Si+1的位置,可以使得最初的投入金额减少。

    接下来,对于任意的序列S1, S2, ... Sn,我们通过反复查看S1S2S2S3.....Sn-1Sn这些相邻的元素的q值的大小,如果qi<qi+1那么就将SiSi+1交换,知道整个序列不再发生变化。
    可以发现,上面的步骤是对原序列以q值为关键字进行冒泡排序,因此,当序列不在发生变化时,对于任意的操作序列,其结果都是唯一的,即以q值递减排列的序列,由于每一次的交换操作都会使得最初投入金额减少,因此,操作完成后,最终的序列对应的初始投入金额一定是最少的。
    所以,我们得到了下面的结论:
    
  对于序列S1, S2, ..., Sn,如果按照q值递减的次序来遍历,那么其初始投入金额最少。 

·子树的p值和q值:
    程序的输入只是给出了每个节点的p值和q值,而对于整个子树的p值和q值,则需要我们在程序中计算。这里的计算逻辑很简单,对于子树的p值,就是遍历子树时的最小初始突入金额,而q值则是初始投入金额减去整个子树的实际消耗费用后的剩余部分。
    所以解答本题的基本思路是,首先遍历根节点,然后递归地遍历它的子树,遍历完成后,每个子树的p值和q值都为已知,然后以q值递减的次序计算当前子树的初始投入金额。最后整棵树的p值就是本题的答案。

    以上是本题的解答思路。

备注:
    需要特别注意的是,题目中的输入仅仅是表示了那两个节点有边,但哪个点是父节点,哪个节点是子节点在输入中是无法体现的。笔者曾因这个问题WA了很久。。。。T-T

输入输出格式:
    输入:
    The first line contains one single integers, n - the number of levels. 

The next n lines describes the levels. Each line contains 2 integers, pi and qi which are described in the statement. 

    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.

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

数据范围:

     
1 <= n <= 10000
     
0 <= q<= p<= 20000
     For 30% of the data, n<=100.
     For 60% of the data, n<=1000.
 
 
程序代码:

/****************************************************//* File        : hiho_week_109                      *//* Author      : Zhang Yufei                        *//* Date        : 2016-07-30                         *//* Description : HihoCoder ACM program. (submit:g++)*//****************************************************/#include<stdio.h>#include<stdlib.h>/* * Define the edge of the graph. * Parameters: *@dst: The destination of the edge. *@next: The next field. */typedef struct node1 {int dst;struct node1 *next;} edge;/* Define the node of tree. * Parameters: *@p: The cost to buy the tower. *@q: The benefit after finishing the tower. *@p_tree: The cost to buy the tree. *@q_tree: The benefit after finishing the tree. *@edges: All the edge of the node. *@visited: Mark if the tower has been visited.  */typedef struct node2 {int p;int q;int p_tree;int q_tree;edge *edges;int visited; } node; // Record all the levels.node *levels;// Input data, the number of levels.int n;// The tmp space used in compute function.node **tmp;/* * The comparison function used in quick sort. * Parameters: *@p1 & p2: The 2 objects to compare. * Returns: *If p1 < p2, returns 1, or if p1 > p2, returns -1, *or returns 0. */int cmp(const void* p1, const void* p2) {node *n1 = *((node**) p1);node *n2 = *((node**) p2);if(n1->q_tree < n2->q_tree) {return 1;} else if(n1->q_tree == n2->q_tree) {return 0;} else {return -1;}}/* * This function computes the initial cost and the terminal benefit * of the tree. * Parameters: *@root: The root of tree. * Returns: *None. */void compute(node* root) {root->visited = 1;edge *cur = root->edges;int sum = root->p - root->q;while(cur != NULL) {node *des = &levels[cur->dst];if(des->visited == 0) {compute(des);sum += des->p_tree - des->q_tree;}cur = cur->next;}int count = 0; cur = root->edges;while(cur != NULL) {tmp[count++] = &levels[cur->dst];cur = cur->next;}if(count > 0) {qsort(tmp, count, sizeof(node*), cmp);root->p_tree = root->p;int height = root->p - root->q;for(int i = 0; i < count; i++) {height += tmp[i]->p_tree;if(root->p_tree < height) {root->p_tree = height;}height -= tmp[i]->q_tree;}root->q_tree = root->p_tree - sum;} else {root->p_tree = root->p;root->q_tree = root->q;}} /* * The main program. */int main(void) {scanf("%d", &n);levels = (node*) malloc(sizeof(node) * n);tmp = (node**) malloc(sizeof(node*) * n);for(int i = 0; i < n; i++) {scanf("%d %d", &levels[i].p, &levels[i].q);levels[i].p_tree = levels[i].q_tree = 0;levels[i].edges = NULL;levels[i].visited = 0;}for(int i = 0; i < n - 1; i++) {int a, b;scanf("%d %d", &a, &b);a--;b--;edge *e1 = (edge*) malloc(sizeof(edge));e1->dst = b;e1->next = levels[a].edges;levels[a].edges = e1;edge *e2 = (edge*) malloc(sizeof(edge));e2->dst = a;e2->next = levels[b].edges;levels[b].edges = e2;}compute(&levels[0]);printf("%d\n", levels[0].p_tree);return 0;}


0 0
原创粉丝点击