题目大意:给你一棵树,让你放最少的东西来覆盖所有的边
这个题目之前写过,就是一个简单的树形dp的板题,因为这个每一个节点都需要挺好处理的。
这个树形dp是从底往根部来递推,所以每一个点,都是由它的根节点来递推的。
如果一个根节点的子节点放了东西,那么这个根节点就可以有选择,但是如果没有放东西,那么这个根节点就必须放东西。
题目大意:给你一棵树,让你用最小的东西来覆盖所有的点。
这个题目和上面那个很像,但是有点不太一样,如果是覆盖所有的点,就不一点要覆盖所有的边,但是如果覆盖了所有的边就肯定会覆盖所有的点。
那么这个题目应该怎么考虑呢,这个同样是一个树形dp,每一个点都是由它的子节点推过来的。
这个就和上面那个有一点不太一样,但是很像。
上面那个对于每一个根节点,只要有一个子节点没有放东西,那么这个根节点肯定要放东西。
而这个题目就不太一样,对于每一个节点,如果想要被覆盖,那么有三种可能,一个是它本身放了东西,一个是它的父节点放了东西,一个是它的子节点放了东西。
所以这个dp对于每一个节点就可以有三种状态。
dp[cur][0]表示这个点被放了东西
dp[cur][1]表示这个点的子节点被放了东西。
dp[cur][2]表示这个点的父节点被放了东西
状态定义好了,这个转移方程就比较好写了。
dp[cur][0]=min(dp[x][0],dp[x][1],dp[x][2]) 如果它被放了东西,那么它的子节点就肯定已经被覆盖了
dp[cur][1]=min(dp[x][0],dp[x][1]) 这个为什么没有dp[x][2]因为我们已经确定cur这个节点不会放东西。
dp[cur][2]=min(dp[x][0],dp[x][1]) 为什么这个dp[x][2]没有呢,理由同上。
但是这个还有一点点小问题要注意
第一个就是dp[cur][0]=1 因为这个点被放了东西,所以要+1
但是dp[cur][1]不需要给她初值1 因为它是由它的子节点推过来 而子节点已经被赋值过了。
#include <cstdio> #include <cstdlib> #include <cstring> #include <queue> #include <vector> #include <algorithm> #include <iostream> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn = 1e4 + 100; int dp[maxn][4]; vector<int>G[maxn]; void dfs(int u,int pre) { // printf("www dp[%d][0]=%d dp[%d][1]=%d dp[%d][2]=%d ", u, dp[u][0], u, dp[u][1], u, dp[u][2]); bool ok = false; int dif = inf; for(int i=0;i<G[u].size();i++) { int v = G[u][i]; if (v == pre) continue; dfs(v, u); } for(int i=0;i<G[u].size();i++) { int v = G[u][i]; if (v == pre) continue; dp[u][0] += min(min(dp[v][1], dp[v][0]), dp[v][2]); dp[u][2] += min(dp[v][1], dp[v][0]); dif = min(dif, abs(dp[v][1] - dp[v][0])); if(dp[v][0]<dp[v][1]) { ok = true; dp[u][1] += dp[v][0]; } else dp[u][1] += dp[v][1]; } if (!ok) dp[u][1] += dif; //printf("dp[%d][0]=%d dp[%d][1]=%d dp[%d][2]=%d ", u, dp[u][0], u, dp[u][1], u, dp[u][2]); } int main() { int n; scanf("%d", &n); for(int i=1;i<n;i++) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } for (int i = 1; i <= n; i++) dp[i][0] = 1; dfs(1, -1); printf("%d ", min(dp[1][0], dp[1][1])); return 0; }