这道题做的我很是头痛,好不容易思路有了结果调了好几个小时也没调对,太弱,最后是找了份题解“对照”人家代码改的才过,索性把他的思路也直接粘过来了。 一看便知是树形dp,dp[i][j]-表示以节点 i 为根的树砍 j 次得到的最小权值,不过这题难就难在dp[u][0]有双重含义。 1.自己的初始权值。(便于转移) 2.以u为根的树砍 0 次得到的最小权值。 这样直接采取 dp[u][j]=dp[son][k]+dp[u][j-k] 就会出现问题,因为dp[son][0]不一定代表的就是其dp含义。 而且 dp[u][j]=min(dp[u][j],dp[son][0]+dp[u][j]); 如果dp[son][0]>0 的话,它是不会被转移的,而实际情况又是它必须转移。 采取的方式是限制 k>0,那么这样就失去了dp[son][0] 的转移,因为u要连son的所有子树的话dp[son][0]又必须得转移,所以采取的是先将dp[son][0]强制转换过来。 还有一点值得注意的是,如果不要哪棵子树的话,dp[u][j]=dp[son][j-k] k的范围为[1,num[k]] . num[k] 以k为根的子树的边。更新答案时,注意如果一个点不是根,那么它至少还得将与父亲节点连的边砍掉,当然还可以砍掉多条边。
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<iostream> #include<vector> #define inf -10000005 #define maxn 1005 using namespace std; vector<int>sons[maxn]; int dp[maxn][30],vis[maxn],num[maxn],k,val[maxn]; void dfs(int rt){ vis[rt]=1; dp[rt][0]=val[rt]; for(int i=0;i<sons[rt].size();i++){ int s=sons[rt][i]; if(vis[s]) continue; dfs(s); num[rt]+=num[s]+1; //记录当前边的条数 for(int j=k;j>=0;j--){ //先将dp[s][0]的值转移过来 dp[rt][j]+=dp[s][0]; //更新将s与rt之间的边切掉的情况 for(int h=1;h<=j;h++) dp[rt][j]=max(dp[rt][j],dp[s][h]+dp[rt][j-h]); //更新不切掉此边的情况 for(int h=1;h<=num[s]+1&&h<=j;h++) dp[rt][j]=max(dp[rt][j],dp[rt][j-h]+0); } } } int cal(int n){ int ans=dp[1][k]; for(int i=2;i<=n;i++) for(int j=1;j<=n-1-num[i]&&j<=k;j++) // 在除了以i为根之外的边上砍j刀(j至少为1) ans=max(ans,dp[i][k-j]); return ans; } void init(){ memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); for(int i=0;i<maxn;i++) for(int j=0;j<=25;j++) dp[i][j]=inf; } int main(){ int i,j,n,a,b; while(~scanf("%d%d",&n,&k)){ for(i=0;i<maxn;i++) sons[i].clear(); for(i=1;i<=n;i++) scanf("%d",&val[i]); for(i=1;i<n;i++){ scanf("%d%d",&a,&b); sons[a].push_back(b); sons[b].push_back(a); } init(); dfs(1); int ans1=cal(n); for(i=1;i<=n;i++) val[i]=-val[i]; init(); dfs(1); int ans2=-cal(n); cout<<ans2<<" "<<ans1<<endl; } return 0; }
zoj cut the tree(树形dp,小细节真的很多)
最新推荐文章于 2024-07-11 21:13:07 发布