洛谷 P 3574 P O I 2014 F A R − F a r m C r a f t (树形 d p ) \Huge{洛谷P3574 POI2014 FAR-FarmCraft(树形dp)} 洛谷P3574POI2014FAR−FarmCraft(树形dp)
文章目录
题目链接:P3574 POI2014 FAR-FarmCraft - 洛谷
题意
给出 n n n个节点、 n − 1 n-1 n−1条边的一棵无根树;经过每条边所用时间都为 1 1 1。然后每个节点都有点权 b i bi bi。
题目故事:管理员从自己家(一号节点)出发,然后将一批电脑分发给每个节点的居民,每个居民得到电脑后会立即开始下载游戏,下载游戏的时间为点权 b i bi bi。
管理员每次经过某节点就相当于直接分发给居民。
管理员的车恰好能够走每条路两遍。
要求计算出管理员从开始配送到所有居民都能玩上游戏所用最少时间。
题目说明
给一棵树,走过每条边需要花费一个时间,安装软件又需要花费一个时间,需要遍历整棵树并回到起点,想让所有点中到达时间+安装时间的最大值最小,问这个值是多少?
思路
跟据题目要求,显然能够知道,题目要求求出所有居民中最后玩上游戏的居民所用时间,并且要求这个时间最小。
跟据题目说明 ,我们可以想到:对于同一个节点的子节点,大致的贪心思路是先遍历点权最大的节点。
但是可能会出现一种情况(画的比较丑陋,多多见谅):

但是我们可以注意到题目有限制条件**"管理员的车恰好能够走每条路两遍"**,那么我们就不用考虑这种情况了。
一般地,我们用 f i fi fi表示节点 i i i这棵子树中的最大值 , d e p i depi depi表示从节点 i i i出发遍历其子树并返回所用的总时间 , b i bi bi表示点权。
容易发现,这道题属于树形dp中的选择节点型,只不过需要判断的是选择子节点的先后顺序。
那么对应的状态转移方程即为:
f x = max ( f x , d e p x + max ( f i , f j + d e p i + 2 ) + 1 ) fx=\max(fx, depx+\max(fi,fj+depi+2)+1) fx=max(fx,depx+max(fi,fj+depi+2)+1)
其中 x x x为当前节点, i , j i,j i,j表示它的其中两个子节点。需要解释一下:
- 方程中的
+2是因为从一个子节点到另一个子节点的两段路。 - 方程中的
+1是因为从 x x x节点到子节点的一段路。
但是该这样进行状态转移,我们需要对子节点相互比较,其时间复杂度为 O ( n 2 ) O(n^2) O(n2),会超时。
我们考虑优化状态转移方程:
-
如果先 i i i后 j j j: max ( f i , f j + d e p i + 2 ) \max(fi,fj+depi+2) max(fi,fj+depi+2)
-
如果先 j j j后 i i i: max ( f j , f i + d e p j + 2 ) \max(fj,fi+depj+2) max(fj,fi+depj+2)
-
因为 f i < f i + d e p j + 2 fi <fi+depj+2 fi<fi+depj+2且 f j < f j + d e p i + 2 fj<fj+depi+2 fj<fj+depi+2
-
原式可化为: max ( f j + d e p i + 2 , f i + d e p j + 2 ) \max(fj+depi+2,fi+depj+2) max(fj+depi+2,fi+depj+2)
-
即为: f i − d e p i < f j − d e p j fi-depi<fj-depj fi−depi<fj−depj(当选 j j j时成立)
因此在判断选取子节点时,可以先对子节点按照上述不等式排序,即为选取子节点的先后顺序。
最后需要注意的是,dfs过程中我们并没有判断根节点的时间,需要输出时进行判断取 m a x max max。
标程
cpp
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
#define int long long
#define ULL unsigned long long
#define PII pair<int, int>
#define lowbit(x) (x & -x)
#define Mid ((l + r) >> 1)
#define ALL(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
const int INF = 0x7fffffff;
const int Mod = 1e9 + 7;
const int N = 5e5 + 10;
int n;
vector<int> a[N];
int b[N], dep[N], f[N];
void dfs(int x, int y) {
if(x != 1) f[x] = b[x];
for(auto i : a[x]) {
if(i == y) continue;
dfs(i, x);
}
sort(ALL(a[x]), [](int n1, int n2) {
return dep[n1] - f[n1] < dep[n2] - f[n2];
});
for(auto i : a[x]) {
if(i == y) continue;
f[x] = max(f[x], f[i] + dep[x] + 1);
dep[x] += dep[i] + 2;
}
}
void Solved() {
cin >> n;
for(int i = 1; i <= n; i ++ ) {
cin >> b[i];
}
for(int i = 1; i < n; i ++ ) {
int x, y; cin >> x >> y;
a[x].push_back(y); a[y].push_back(x);
}
dfs(1, 0);
cout << max(f[1], dep[1] + b[1]) << endl;
}
signed main(void) {
IOS
int ALL = 1;
// cin >> ALL;
while(ALL -- ) Solved();
// cout << fixed;//强制以小数形式显示
// cout << setprecision(N); //保留n位小数
return 0;
}