zoj 3506 Cut the Tree (好多细节的树形dp 终于把你搞懂了)

Cut the Tree

Time Limit: 2 Seconds      Memory Limit: 65536 KB

Given a graph with N vertices (labeled 1 to N). The graph is connected, undirected and acyclic, and also known as an "unrooted tree". Each vertex has a weight, which is an integer. The total weight of a tree, is the summation of the weight of all its vertices.

Follow the instruction below to "cut the tree" exactly K times:

(1) Choose an edge of the tree, and remove it.
(2) Choose and discard one of the two divided parts of the graph we get.
(3) Get a new unrooted tree.

Now you are asked to calculate, after all those done, the minimal and maximal weight of the tree we finally get.

Input

Mutiple test cases, process to the end of file.

Each test case consists of three parts.

First part, a single line with two integers N (1<=N<=1000) and K (0<=K<=20, K<N).

Second part, a single line with N integers, the weight of vertices labeled 1 to N. The weight is in range [-1000,1000].

Third part, N-1 lines, each line with two integers X and Y represents an edge between vertex X and vertex Y.

Output

For each test case, output one line with two integers, the minimal and maximal weight of the tree we finally get.

Sample Input

2 1
-1 1
1 2
6 0
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6
6 1
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6

Sample Output

-1 1
3 3
-1 4




题意:
给你一棵树,有n个节点,每个节点有一个权值,恰好做k次cut操作,每次可以cut一条边,然后丢掉其中一部分而得到一棵新的树,求最后得到的树的最小/最大权值和。

思路:
一看便知是树形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 <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#pragma comment (linker,"/STACK:102400000,102400000")
#define maxn 1005
#define MAXN 2005
#define mod 1000000009
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

int n,m,ans,cnt,tot,flag;
int pp[maxn],cost[maxn],num[maxn],dp[maxn][25];
struct Node
{
    int v,w,next;
} edge[MAXN];

void addedge(int u,int v,int w)
{
    cnt++;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=pp[u];
    pp[u]=cnt;
}
void dfs(int u,int pre)
{
    int i,j,k,v,t;
    dp[u][0]=cost[u];
    num[u]=0;
    for(i=1; i<=m; i++)
    {
        dp[u][i]=INF;
    }
    for(i=pp[u]; i; i=edge[i].next)
    {
        v=edge[i].v;
        if(v==pre) continue ;
        dfs(v,u);
        num[u]+=num[v]+1;   // 以v为根的边及u-v这条边
        for(j=m; j>=0; j--)
        {
            dp[u][j]+=dp[v][0];  // 将dp[v][0]先强制转移过来
            for(k=1; k<=j; k++)  // k!=0 dp[v][0]不转移两次
            {  // 如果都在这里转移的话会产生 dp[u][j]转移不对的情况 dp[v][0]>0的话dp[u][j]就会不要它了
                dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]);
            }
            for(k=1; k<=num[v]+1&&k<=j; k++)
            {
                dp[u][j]=min(dp[u][j],dp[u][j-k]);
            }
        }
    }
}
int solve()
{
    int i,j,t;
    dfs(1,0);
    ans=dp[1][m];
    for(i=2; i<=n; i++)
    {
        for(j=1; j<=n-1-num[i]&&j<=m; j++) // 在除了以i为根之外的边上砍j刀(j至少为1)
        {
            ans=min(ans,dp[i][m-j]);
        }
    }
    return ans;
}
int main()
{
    int i,j,t,u,v;
    while(~scanf("%d%d",&n,&m))
    {
        for(i=1; i<=n; i++)
        {
            scanf("%d",&cost[i]);
        }
        cnt=0;
        memset(pp,0,sizeof(pp));
        for(i=1; i<n; i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v,0);
            addedge(v,u,0);
        }
        int x=solve();
        for(i=1; i<=n; i++)
        {
            cost[i]=-cost[i];
        }
        int y=-solve();
        printf("%d %d\n",x,y);
    }
    return 0;
}
/*
6 2
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6
6 3
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6
6 4
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6
6 5
-3 1 -2 3 4 0
1 2
1 3
2 4
3 5
3 6

-5 4
-5 4
-5 4
-3 4
*/







  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值