紫书 例题 9-14 UVa 1218 (树形dp)
这道题有个初始值设成1e9, 然后这个值是要加很多次的,然后就会溢出变成负数,
然后就一直WA, 找这个bug找了一个小时……以后不能随便这样设那么大,
要考虑会不会加很多次然后溢出。
讲一下思路。
首先对于当前节点u,可以分三种情况。
一种是当前节点是服务器,一种是节点的父亲是服务器,一种是节点的儿子是服务器。
不可能都不是服务器,因为如果这样的话u自己就没有服务器相连。
设本身是服务器的为d(u, 0),父亲是服务器为d(u, 1),儿子是服务器是d(u, 2)
那么我们可以写出转移方程
d(u, 0):此时u的儿子可以是服务器也可以不是服务器,所以d[u][0] = sum{min(d(v, 0),d(v, 1)) | v是u的儿子} + 1(本身)
d(u, 1):此时u的儿子全部都不是服务器,因为父亲已经是,只能和一个服务器相连。,所以d(u, 1) = sum{d(v, 2)| v是u的儿子}
d(u, 2):此时u的节点中一定有且只有一个儿子有服务器,所以我们可以枚举这个儿子是哪一个,设这个儿子是q, 其他的所有儿子是p, 那么d(u, 2) = min(d(q, 0) + sum(d(p, 2)))
但是这样非常的耗时间,我们可以利用前面的式子代换一下。
我们观察 sum(d(p, 2)), p是除了q以外的所有儿子,所以可以用所有儿子的值减去q的值,
也就是sum(d(p, 2)) = sum(d(v, 2)) - d(q, 2) = d(u, 1) - d(q, 2)。所以枚举q取最小的就好了。
以0为根节点,最后输出min(d(0, 0), d(0, 2)), 根节点没有父节点,所以d(0, 1)不理他
#include<cstdio>
#include<vector>
#include<algorithm>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 11234;
int d[MAXN][3], n;
vector<int> g[MAXN];
void dfs(int u, int fa)
{
d[u][1] = 0; d[u][0] = 1;
if(g[u].size() == 1 && fa != -1) return;
REP(i, 0, g[u].size())
{
int v = g[u][i];
if(v == fa) continue;
dfs(v, u);
d[u][1] += d[v][2];
d[u][0] += min(d[v][1], d[v][0]);
}
REP(i, 0, g[u].size())
{
int v = g[u][i];
if(v == fa) continue;
d[u][2] = min(d[u][2], d[u][1] - d[v][2] + d[v][0]);
}
}
int main()
{
while(~scanf("%d", &n))
{
REP(i, 0, n) g[i].clear(), d[i][2] = MAXN + 1;
REP(i, 0, n - 1)
{
int a, b;
scanf("%d%d", &a, &b);
a--; b--;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(0, -1);
printf("%d\n", min(d[0][0], d[0][2]));
scanf("%d", &n);
}
return 0;
}