POJ

传送门

有一颗N个结点的树,求需要割掉至少多少条边才能得到一颗恰好有P个结点的树

没做出来,看了大神的解法才懂怎么做的orz

用了dp[root][i]表示用一颗以root为根的树,需要割点多少条边才能得到以root为根的恰好有i个结点的树。这样以来就有子问题,可以进行dp。

最后的答案里,由于在这个过程中,我们默认了root为根,但如果实际它有父亲,得到的dp[root][P]应该再加一

状态转移:dp[root][i] = min(dp[root][i - k] + dp[child][k] - 1,dp[root][i]);

每次在dp时,边界的话是,dp[root][1] = G[root].size();(儿子的数量),此时我们对每个结点的值,已经将它与儿子的边割掉。对于root的第一颗子树,假设子树上有cnt个结点,将子树连接上来,那么这时我们处理的是一颗有cnt+1个结点的树,会得到dp[root][i]({i} ⊆[1,cnt + 1],当i>cnt+1时,值取INF。在接下来的子树中,就可以利用上前面的子树与根结点构成的树的值。

实际的操作过程中,是先得到一个根结点root,然后将它的一颗颗子树连接上来,并更新值,所以已经得到的树中取i-k个结点的值加上新子树取k个结点的值,最后才应该再减一,表示复原原本两个root与child间的边。

 1 #include <vector>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #define INF 0x3f3f3f3f
 6 using namespace std;
 7 
 8 const int maxn = 155;
 9 vector<int> G[maxn];
10 int deg[maxn], sum[maxn];
11 int dp[maxn][maxn];
12 int N, M;
13 
14 
15 void dfs(int node) {
16     for (int i = 0; i < G[node].size(); i++) {
17         int u = G[node][i];
18         dfs(u);
19         sum[node] += sum[u];
20         for (int j = sum[node]; j > 0; j--) {
21             for (int s = 1; s < j; s++) {
22                 dp[node][j] = min(dp[node][j], dp[node][j - s] + dp[u][s] - 1);
23             }
24         }
25     }
26 }
27 
28 int main(int argc, const char * argv[]) {
29     scanf("%d%d", &N, &M);
30     for (int i = 0; i <= N; i++) G[i].clear();
31     memset(dp, INF, sizeof(dp));
32     for (int i = 1; i < N; i++) {
33         int u, v;
34         scanf("%d%d", &u, &v);
35         G[u].push_back(v);
36         ++deg[u];
37     }
38     for (int i = 1; i <= N; i++) {
39         dp[i][1] = deg[i];
40         sum[i] = 1;
41     }
42     dfs(1);
43     int ans = dp[1][M];
44     for (int i = 2; i <= N; i++) {
45         ans = min(ans, dp[i][M] + 1);
46     }
47     printf("%d
", ans);
48     return 0;
49 }
原文地址:https://www.cnblogs.com/xFANx/p/7495552.html