开学A的第一道题还是1A,感觉还是很开心。
设dp[i][k]为以i为子树的情况下变成一个子树节点为k的最少需要减掉的边数。那么对每个子树背包就可以了,注意的是刚开始需要将dp[a][1] = a != 1?num[a]-1:num[a];的意思是把a的与孩子连的边全部去掉时的情况,然后一步一步背包加边。
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; const int maxa = 155; int edge[maxa][maxa]; int num[maxa]; int dp[maxa][maxa]; //包括i且只考虑i的子树的所有最小情况, int nt[maxa]; //以i为根的字数的节点数 int ans; int n, p; int dp1[maxa]; int dfs(int a, int fa){ nt[a] = 1; for(int i = 0; i < num[a]; i++){ int k = edge[a][i]; if(k != fa){ dfs(k, a); } } dp[a][1] = a != 1?num[a]-1:num[a]; for(int i = 0; i < num[a]; i++){ int k = edge[a][i]; if(k != fa){ for(int j = 1; j <= nt[a] + nt[k]; j ++){ dp1[j] = dp[a][j]; } for(int j = 1; j <= nt[a]; j++){ for(int h = 1; h <= nt[k]; h++){ dp1[j+h] = min(dp1[j+h], dp[a][j] + dp[k][h]-1); } } for(int j = 1; j <= nt[a] + nt[k]; j ++){ dp[a][j] = dp1[j]; } nt[a] += nt[k]; } } if(a == 1){ ans = min(ans, dp[a][p]); }else ans = min(ans, dp[a][p] + 1); } int main(){ while(scanf("%d%d", &n, &p)!=EOF){ int a, b; memset(num, 0, sizeof(num)); for(int i = 1; i < n; i++){ scanf("%d%d", &a, &b); edge[a][num[a]++] = b; edge[b][num[b]++] = a; } for(int i = 1; i <= n; i++){ for(int k = 1; k <= n; k++){ dp[i][k] = maxa; } } ans = maxa; dfs(1, 0); printf("%d ", ans);/* for(int i = 1; i <= n; i++){ printf("%d %d * ", nt[i], i); for(int k = 1; k <= n; k++){ printf("%d ", dp[i][k]); }printf(" "); }*/ } } /* 11 6 1 2 1 3 1 4 1 5 2 6 2 7 2 8 4 9 4 10 4 11 11 1 1 2 1 3 1 4 1 5 2 6 2 7 2 8 4 9 4 10 4 11 */