样例:
输入
2 7 13 20 1 20 6 9 8 1 2 1 3 2 4 2 5 3 6 5 7 4 1 17 13 4 1 2 1 3 3 4
输出
27 5
题意简述:
在一个图上, 一个路由器可以覆盖当前这个节点以及和他相邻的节点, 每个节点部署都有相应的花费, 问你用路由器把这个图全部覆盖掉的最小花费。
思路分析:
很显然这是一道树形DP的题。对于这种题一般要分析一下状态转移方程怎么来的。
使用Y氏动态规划分析法
状态表示:
集合:f[i][0]表示以i为根节点,当前这个节点被父节点看着的情况
f[i][1]表示以i为根节点,当前这个节点被子节点看着的情况
f[i][2]表示以i为根节点,当前这个节点被自己看着的情况
属性:最小值
状态计算:
三种情况:
情况一:假设这个节点被父节点看着,那么这个节点是可以不放的,所以他的子节点就只有两种情况,被子节点看着或者自己看着自己,两者取最小值。
这种的状态方程为:f[i][0] += min(f[j][1], f[j][2])
注:i为当前的节点, j为子节点
情况二:假设这个节点被子节点看着,那么这个节点的子节点是必须要放的,那么只要让他的子节点的其中一个子节点放就行了.
所以 状态转移的方程为:f[i][1] = min( f[j][2] + sum(min( f [k][1],f[k][2] ) ) ))
注:i是当前的节点, j是i的子节点, k是 i的除了j的子节点的, 因为j一定放,所以其他的子节点一定不放(因为子节点只放一个),所以其他子节点有两种状态,一种是被他的父节点看着(这个子节点可能有其他的父节点) ,也可以被自己看着(反正就是不放)。这样就会发现和情况一有点类似。
情况三, 这个节点被自己看着。也就是他的子节点父节点都是可有可不有的,所以让那三种情况取最小值就行了。
即状态转移方程为:f[i][2] += min(f[j][2],f[j][0], f[j][1]);
注:i是当前的节点,j是子节点
特殊处理:情况二方程进一步化简,
算完一轮的f[i][0]表示所有子节点要么让自己的父节点看着,要么自己看着自己的所有情况
即f[i][0] = sum(min(f[j][1], f[j][2]));i为当前的节点, j为子节点
所以只让其中一个子节点j放的情况,即除了j节点放,其他的节点可放,或者让他自己的子节点看着的情况由 f[j][2] + sum(min( f [k][1],f[k][2] ) 其中sum(min( f [k][1],f[k][2] )意思为其他子节点无父亲节点了,他这里可以被子节点看着的情况,也就是可以转化为
所有的情况一中扣去假设j有父亲节点,这个节点可以自己看着自己或者让他的子节点看着的情况, 所以公式转换为 sum(min( f [k][1],f[k][2] )= f[i][0] - min(f[j][1], f[j][2]).
所以最终的式子化为 f[i][1] = min( f[j][2] + f[i][0] - min(f[j][1], f[j][2])这样就优化了一层循环。
具体代码和注释如下
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e3 + 10; int f[N][3], w[N]; vector<int> vc[N]; int isroot[N]; void dfs(int u, int fa) //f[u][0] 父节点看着u { //f[u][1] 子节点看着u f[u][0] = 0;f[u][2] = w[u]; //f[u][2] 自己看着自己u f[u][1] = 1e9; for(auto v : vc[u]) { if(fa == v) continue; dfs(v, u); f[u][0] += min(f[v][2], f[v][1]);//v没有父节点看着 f[u][2] += min(f[v][2], min(f[v][0], f[v][1]));//所有情况都有 } for(auto v : vc[u])//v节点上放 //w[v] + sum(min(f[k][1], f[k][2])), k表示除v以外的节点 { f[u][1] = min(f[u][1], f[v][2] + f[u][0] - min(f[v][1], f[v][2]) );//这个节点放, 并且其他节点没有父节点看着的状态总数 } } void solve() { int n; cin >> n; for(int i = 1; i <= n; i ++) vc[i].clear(); for(int i = 1; i <= n; i ++) cin >> w[i]; for(int i = 1; i < n; i ++) { int u, v; cin >> u >> v; vc[u].push_back(v); isroot[v] = 1; } int root = 1; while(isroot[root]) root ++; dfs(root, -1); if(n == 1) { cout << w[root]; } else { cout << min(f[root][2], f[root][1]);//因为这个节点没有父节点,所以没有不用考虑父节点放的情况 } cout << "\n"; } int main() { int t; cin >> t; while (t--) { solve(); } return 0; }