POJ 2152 Fire (树形DP,经典)

题意:给定一棵n个节点的树,要在某些点上建设消防站,使得所有点都能够通过某个消防站解决消防问题,但是每个点的建站费用不同,能够保证该点安全的消防站的距离上限也不同。给定每个点的建站费用以及最远的消防站距离上限,求保证该树安全的最小花费。

思路:

  要选择部分点来建站解决消防问题,而总花费是有最优解的。

  如何进行树形DP?

  假设某点t的所有子树的消防问题都解决,而且已经获得最优解了,那么现在考虑的是点t的最优解问题,点t可以依靠任何点只要不超过距离限制即可,那枚举一下所有点试试,一旦t依靠某个点j解决消防问题,那么t的孩子/孙子也可能可以依靠j来解决消防问题。这只需要判断一下每个孩子是否能够依靠j就行了,而孙子如果依靠j的话,如何知道?此时,之前已经假设t的孩子的消防问题已经解决,那么t的孩子在枚举依靠站时也肯定枚举过j了,或者能依靠,或者不能依靠。而t的孙子能不能依靠j,是交给t的孩子去解决的,就像t的孩子是交给t来负责一样。这是符合递归性质的,那么当前我们就可以只考虑点t和他的孩子们能不能愉快玩耍就行了。

  但是还有个最优性质,那么在枚举j的基础上,dp[t][j]=w[j]是至少的,然后再加上所有子树的最优花费,就是dp[t][j]了。上面是领会思路的。下面看别人的状态方程。

  复杂度为O(n^2)的树形DP.因为要依赖其他站点,所以不仅仅只从子树中获取信息,也可能从父亲结点,兄弟结点获取信息,所以在计算每个点时首先想到要枚举,因为n特别小,允许我们枚举。设dp[i][j]表示i点及其子树都符合情况下i点依赖j点的最小花费,有了这个似乎还不够,再开个一维数组best,best[i]表示以i为根的子树符合题目要求的最小花费。这样状态转移方程就是dp[i][j] = cost[j] + sum(min(dp[k][j]-cost[j],best[k])) (k为i的子节点,j为我们枚举的n个点),因为i的每个子节点可以和i一样依赖j结点,那么花费是dp[k][j]-cost[j],或者依赖以k为根的树中的某点,花费是best[k],最后再加上cost[j],因为要在j结点建站所以要增加花费。

  为什么是O(n2)?递归计算每个节点时的复杂度为O(孩子数*n),而每个节点作为孩子节点来计算的话仅有1次。即总复杂度最高为O(n2)。

 1 //#include <bits/stdc++.h>
 2 #include <vector>
 3 #include <iostream>
 4 #include <cstdio>
 5 #include <cstring>
 6 #define pii pair<int,int>
 7 #define INF 0x3f3f3f3f
 8 #define LL long long
 9 using namespace std;
10 const int N=1010;
11
12 struct node
13 {
14     int from,to,len;
15     node(){};
16     node(int from,int to,int len):from(from),to(to),len(len){};
17 }edge[N*2];
18 int edge_cnt, w[N], d[N], n;
19 vector<int> tree[N];
20 void add_node(int from,int to,int len)
21 {
22     edge[edge_cnt]=node(from,to,len);
23     tree[from].push_back(edge_cnt++);
24 }
25
26 int dist[N][N], root;    //到根的距离
27 void get_dis(int t,int far,int len)
28 {
29     dist[root][t]=dist[t][root]=len;
30     if(len>d[root]) return ;
31     for(int i=0; i<tree[t].size(); i++)
32     {
33         node &e=edge[tree[t][i]];
34         if(e.to!=far)    get_dis(e.to,t,len+e.len);
35     }
36 }
37
38
39 int dp[N][N], best[N];
40 void DFS(int t,int far)
41 {
42     for(int i=0; i<tree[t].size(); i++) //递归先解决子问题
43     {
44         node &e=edge[tree[t][i]];
45         if(e.to!=far)    DFS(e.to,t);
46     }
47
48     best[t]=INF;
49     for(int j=1,sum=0; j<=n; j++,sum=0)     //尝试将i依靠j解决消防问题
50     {
51         if( dist[t][j]>d[t] )   continue;   //不能依靠到j,太远了
52         for(int i=0; i<tree[t].size(); i++) //每个子节点取最优解
53         {
54             node &e=edge[tree[t][i]];
55             if(e.to!=far)    sum+=min(best[e.to], dp[e.to][j]-w[j]);
56         }
57         dp[t][j]=w[j]+sum;
58         best[t]=min(best[t], dp[t][j]);
59     }
60 }
61
62 void init()
63 {
64     edge_cnt=0;
65     for(int i=1; i<=n; i++) tree[i].clear();
66     memset(dp, 0x3f, sizeof(dp));
67     memset(dist, 0x3f, sizeof(dist));
68 }
69
70 int main()
71 {
72     //freopen("input.txt", "r", stdin);
73     int t,a,b,c;
74     cin>>t;
75     while(t--)
76     {
77         scanf("%d",&n);
78         init();
79         for(int i=1; i<=n; i++) scanf("%d",&w[i]);
80         for(int i=1; i<=n; i++) scanf("%d",&d[i]);  //距离i城市最远的消防站距离上限
81         for(int i=1; i<n; i++)
82         {
83             scanf("%d%d%d",&a,&b,&c);
84             add_node(a,b,c);
85             add_node(b,a,c);
86         }
87         for(int i=1; i<=n; i++)    get_dis(root=i, -1, 0);  //计算任意点对之间的距离
88         DFS(1, -1);
89         printf("%d\n",best[1]);
90
91     }
92     return 0;
93 }

AC代码

时间: 2024-10-09 00:18:33

POJ 2152 Fire (树形DP,经典)的相关文章

POJ 2152 Fire(树形DP)

题意: 思路:令F[i][j]表示 的最小费用.Best[i]表示以i为根节点的子树多有节点都找到负责消防站的最小费用. 好难的题... 1 #include<algorithm> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 int tot,go[200005],first[200005],next[200005],val[2000

poj2152(Fire) 树形DP

题目链接:http://poj.org/problem?id=2152 题意:一棵带边权的树,边权表示节点间距离,在i上建立消防站的代价是w[i],如果在一点i没建消防站,那么它与距离这个点最近的消防站之间的距离不能大于d[i].问满足建站最小的花费: 解法:看了陈启峰的论文才会的,感觉挺难的,不过论文里分情况讨论了,应该不需要:dp[i][j]表示在i处选择j处作为供应站(但是并不一定要求是最近的,这样即使有更近的也无所谓,和论文里有出入.),best[i]表示i及其子树满足要求的最小花费,那

POJ 2342 (树形DP)

Anniversary party Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3863   Accepted: 2172 Description There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The University has a hierarchical structure

树形DP经典题

题目传送门 题意: 给出一棵树,求离每个节点最远的点的距离 思路: 把无根树转化成有根树分析, 对于上面那棵树,要求距结点2的最长距离,那么,就需要知道以2为顶点的子树(蓝色圈起的部分,我们叫它Tree(2)),距顶点2的最远距离L1 还有知道2的父节点1为根节点的树Tree(1)-Tree(2)部分(即红色圈起部分),距离结点1的最长距离+dist(1,2) = L2,那么最终距离结点2最远的距离就是max{L1,L2} f[i][0],表示顶点为i的子树的,距顶点i的最长距离 f[i][1]

POJ 1947 Rebuilding Roads (树形dp 经典题)

Rebuilding Roads Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 9499   Accepted: 4317 Description The cows have reconstructed Farmer John's farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The

POJ 1155 TELE 背包型树形DP 经典题

由电视台,中转站,和用户的电视组成的体系刚好是一棵树 n个节点,编号分别为1~n,1是电视台中心,2~n-m是中转站,n-m+1~n是用户,1为root 现在节点1准备转播一场比赛,已知从一个节点传送数据到达另一个节点,电视台需要一定的费用 若可以传送数据到达用户的节点n-m+1~n,这些用户各自愿意支付一定的费用给电视台 现在电视台希望在不亏本的情况下为尽量多的用户转播比赛 输出最多可以为多少用户转播比赛 背包类型的树形DP第一题 dp[i][j]表示以节点i为根的子树有j个用户获得转播,电视

HDU 2196 Computer 树形DP经典题

链接:http://acm.hdu.edu.cn/showproblem.php? pid=2196 题意:每一个电脑都用线连接到了还有一台电脑,连接用的线有一定的长度,最后把全部电脑连成了一棵树,问每台电脑和其它电脑的最远距离是多少. 思路:这是一道树形DP的经典题目.须要两次DFS,第一次DFS找到树上全部的节点在不同子树中的最远距离和次远的距离(在递归中进行动态规划就可以),第二次DFS从根向下更新出终于答案.对于每次更新到的节点u,他的最远距离可能是来自u的子树,或者是u的父亲节点的最远

HDU 2196 Computer 树形DP 经典题

给出一棵树,边有权值,求出离每一个节点最远的点的距离 树形DP,经典题 本来这道题是无根树,可以随意选择root, 但是根据输入数据的方式,选择root=1明显可以方便很多. 我们先把边权转化为点权,放在数组cost中 令tree(i)表示以节点i为根的子树 对于节点i,离该节点最远的点要不就是在tree(i)中,要不就是在father(i)上面 令: dp[i][1] : 在子树tree(i)中,离i最远的距离 dp[i][2] : 在子树tree(i)中,离i第二远的距离 (递推的时候需要)

POJ 2486-Apple Tree(树形DP)(难)

题意:一颗树,n个点(1-n),n-1条边,每个点上有一个权值,求从1出发,走V步,最多能遍历到的权值 思路:(思路转自http://blog.csdn.net/libin56842/article/details/10101807) 树形dp,比较经典的一个树形dp.首先很容易就可以想到用dp[root][k]表示以root为根的子树中最多走k时所能获得的最多苹果数,接下去我们很习惯地会想到将k步在root的所有子结点中分配,也就是进行一次背包,就可以得出此时状态的最优解了,但是这里还有一个问