题意:输入N,P,求一棵N个节点的树最多砍多少次边,能得到一个节点数为P的树。
这个题目还真是一时间考住我了,我怎么都没想通怎么DP,甚至后来没办法去找博客看了一下,还是觉得无法理解。今天上午,翘着水课来刷题,总算是弄懂了。
用dp[rt][j]表示在以rt为根节点的树上,要保留j个节点,(每次j从p递减到1)需要的最小割边数,首先我们来考虑边界,如果是叶子,则dp[rt][1]=0是显而易见的,除此外,它无法构成任何其他数目节点的树,所以dp[rt][i](i为除1外的数)全部置为INF,以此来表示不存在。
dp[rt][j]=min(dp[rt][j]+1,dp[rt][j-k]+dp[nx][k])
即,每次遍历一个子树,如果割掉这个子树,就直接+1(之前我一度以为是要先减去该子树的节点数目后再+1,但其实j就是节点数,你管它能不能形成,不能形成说明本身当前节点数还不够,反正是INF,不影响)。如果不割掉,就把j进行分流,看看分多少个节点给nx最佳
发现这类题目,就是01背包的翻版,每次访问子树,都把j从p到1遍历一下,如果根本总体还不够这么多节点,就是INF,反正无影响,如果达到了这个节点量,说明在此子树遍历前,已经有一个暂时的最优解,就进行节点分流,找出当前最优解。。。强调一点,dp[rt][0]必须设置为INF,因为当j=1时,给子树的分流节点没有,但是此时必须割掉子树才能达到dp[rt][1]的效果。。总之,凡是不存在的状态,均用INF表示
#include <iostream> #include <cstdio> #include <cstring> #include <vector> using namespace std; int dp[200][200]; int vis[200]; vector<int> v[200]; int n,p; void clean() { for (int i=0;i<=n+5;i++){ v[i].clear(); } } void dfs(int rt) { vis[rt]=1; int r=v[rt].size(); for (int i=0;i<r;i++){ int nx=v[rt][i]; if (vis[nx]) continue; dfs(nx); for (int j=p;j>=1;j--){ int temp=dp[rt][j]+1; for (int k=0;k<j;k++){ temp=min(temp,dp[rt][j-k]+dp[nx][k]); } dp[rt][j]=temp; } } } int main() { while (scanf("%d%d",&n,&p)!=EOF) { clean(); int i,j; for (i=1;i<n;i++){ int a,b; scanf("%d%d",&a,&b); v[a].push_back(b); v[b].push_back(a); } memset(vis,0,sizeof vis); for (i=0;i<=n+5;i++){ for (j=0;j<=n+5;j++){ dp[i][j]=10000000; } dp[i][1]=0; } dfs(1); int ans=dp[1][p]; for (i=2;i<=n;i++){ ans=min(ans,dp[i][p]+1);//如果最优解不是在节点1取到,意味着在最优解的那个点x肯定把1-x给切掉了,故需要+1 } printf("%d ",ans); } return 0; }