有一颗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 }