[树形DP] 手机网络

· · 题解

题目

手机网络

题解

首先我们确定这道题是树形DP,而且可以看出这道题和树的最大独立集有几分相似

那么我们就先打一个树的最大独立集出来,但我们发现这样并不能AC

因为树的最大独立集可以全部由结点的儿子转移过来,而这道题还有另一种可能,就是有结点的父亲转移过来,所以我们不能像最大独立集那样定义状态,我们还需要多定义一个状态

那么我们就可以分情况对这三种状态进行状态转移方程的讨论: 当$j$等于$0$时,因为$i$点有一个通讯塔,所以$son(i)$有可能从它的父亲(也就是$i$点)转移过来,也有可能有它的儿子转移过来,当然,也有可能它自己本身就有一个通讯塔,即 $$dp[i][0]=\sum Min(dp[son(i)][0],dp[son(i)][1],dp[son(i)][2])+1$$ (因为我们在i结点建立了一个通讯塔,所以要加1) 当$j$等于$1$时,因为$i$点没有通讯塔,所以$son(i)$不可能从它的父亲转移过来,它只有可能由它的儿子转移过来,或者是它自己有一个通讯塔,即 $$dp[i][1]=\sum Min(dp[son(i)][0],dp[son(i)][2])$$ 当$j$等于$2$时,因为$i$点没有通讯塔,所以$son(i)$也不可能从它的父亲转移过来,它只有可能由它的儿子转移过来,或者是它自己有一个通讯塔,即 $$dp[i][2]=\sum Min(dp[son(i)][0],dp[son(i)][2])$$ 但这样写我们很容易发现一个问题,如果$dp[i][2]$全是从$dp[son(i)][2]$转移过来的话,就证明它的所有儿子都没有通讯塔,那么就不可能将它覆盖,如何避免这种情况呢? 其实我们只需要记录下每一次$dp[son(i)][0]-dp[son(i)][2]$的差值,再取一个$min$值,简单的来说,就是 $$p=Min(dp[son(i)][0]-dp[son(i)][2])$$ 在最后,如果我们发现$dp[i][2]$全是从$dp[son(i)][2]$转移过来的话,就再加上一个$p$,就相当于将其中的一个$dp[son(i)][2]$强制转换为了$dp[son(i)][0]$,同时还保证了最小 综上所述 $$dp[i][j]=\begin{cases} \sum Min(dp[son(i)][0],dp[son(i)][1],dp[son(i)][2])+1(j==0) \\ \sum Min(dp[son(i)][0], dp[son(i)][2])(j==1) \\ \sum Min(dp[son(i)][0], dp[son(i)][2])(j==2\ and\ flag==1) \\ \sum Min(dp[son(i)][0], dp[son(i)][2])+p(j==2\ and\ flag==0) \end{cases}$$ $flag$表示$dp[i][2]$是否由一个或多个$dp[son(i)][0]$转移过来 值得注意的是,最后输出的是$Min(dp[root][0],dp[root][2])$,因为最后的根结点是不可能有父亲的 ---- # 代码 ```cpp #include <cstdio> #include <cstring> #include <vector> using namespace std; #define reg register template <class T> inline T read() { T x = 0; T f = 1; char s = getchar(); while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();} while(s >= '0' && s <= '9') {x = (x << 3) + (x << 1) + s - 48; s = getchar();} return x * f; } template <typename T> inline void wri(T x) { if(x < 0) {x = -x; putchar('-');} if(x / 10) wri(x / 10); putchar(x % 10 + 48); } template <typename T> inline void write(T x, char s) { wri(x); putchar(s); } template <typename T> inline T Min(T x, T y) {return x < y ? x : y;} #define MAXN 10005 #define INF 0x3f3f3f3f vector <int> G[MAXN]; int n; int dp[MAXN][3]; bool vis[MAXN]; inline void dfs(int x) { vis[x] = 1; bool flag = 0; int siz = G[x].size(), p = INF; for(reg int i = 0;i <= siz - 1;i ++) if(! vis[G[x][i]]) { dfs(G[x][i]); dp[x][0] += Min(dp[G[x][i]][0], Min(dp[G[x][i]][1], dp[G[x][i]][2])); //状态转移 dp[x][1] += Min(dp[G[x][i]][0], dp[G[x][i]][2]); if(dp[G[x][i]][0] <= dp[G[x][i]][2]) { flag = 1; dp[x][2] += dp[G[x][i]][0]; } else { p = Min(p, dp[G[x][i]][0] - dp[G[x][i]][2]); dp[x][2] += dp[G[x][i]][2]; } } if(! flag) dp[x][2] += p; dp[x][0] ++; //如果自己有通讯塔,要加上自己的那一个 } int main() { n = read<int>(); for(reg int i = 1;i <= n - 1;i ++) { int a = read<int>(), b = read<int>(); G[a].push_back(b); //用邻接表储存这棵树 G[b].push_back(a); } dfs(1); //dfs求解DP write(Min(dp[1][2], dp[1][0]), '\n'); return 0; } ```