1. 程式人生 > 實用技巧 >二叉蘋果樹

二叉蘋果樹

題目

Description

有一棵蘋果樹,如果樹枝有分叉,一定是分2叉(就是說沒有隻有1個兒子的結點)。
這棵樹共有N個結點(葉子點或者樹枝分叉點),編號為1-N,樹根編號一定是1。
我們用一根樹枝兩端連線的結點的編號來描述一根樹枝的位置。下面是一顆有4個樹枝
的樹:
2 5
\ /
3 4
\ /
1

現在這顆樹枝條太多了,需要剪枝。但是一些樹枝上長有蘋果。

給定需要保留的樹枝數量,求出最多能留住多少蘋果。注意樹根不能剪沒了喲。

Input

第1行2個數,N和Q(1<=Q<=N,I<N<=IOO)。
N表示樹的結點數,Q表示要保留的樹枝數量。

接下來N-I行描述樹枝的資訊。 每行3個整數,前兩個是它連線的結點的編號。第3個數是這根樹枝上蘋果的數量。 每根樹枝上的蘋果不超過30000個。

Output

一個數,最多能留住的蘋果的數量。

Sample Input

5 2
1 3 1
1 4 10
3 2 20
3 5 20 

Sample Output

21

思路

一道樹形dp的題目;

我們可以設dp[x][i]為以x為根的節點的兒子邊(也就是i節點以下的所有邊),保留了i條邊;

題目是一顆二叉樹所有每次我們只需考慮兩個兒子的資訊,然後將資訊合併到根節點;

那麼考慮兩種情況:

1,只有一個兒子節點保留全部的邊,也就是另一個兒子節點沒有保留邊i=0;

那麼dp[x][i]=max(dp[leftx][i-1]+v[x][leftx], dp[rightx][i-1]+v[x][rightx]);

(leftx表示左兒子,rightx表示右兒子,v[][]表示兩點之間的權值);

那為什麼兒子保留的邊要減一dp[leftx][i-1], 因為x到leftx之間也有一條邊,兒子下面的邊怎麼可能和父節點一樣呢;

2,列舉x節點保留的邊i中,分配到左邊有多少條邊j(j<i);

左兒子leftx保留了多少邊j-1;

但是x到leftx之間也有一條邊所有j要減一,那麼右兒子rightx 保留邊數就是i-j-1;

那麼dp[x][i]=max(dp[leftx][j-1]+v[x][leftx]+dp[rightx][i-j-1]+v[x][rightx], dp[i][j]);

(leftx表示左兒子,rightx表示右兒子,v[][]表示兩點之間的權值);

那麼轉移方程就是

    for(ll i=1;i<=q;i++)//列舉保留邊數
    for(ll j=0;j<=i;j++)//列舉分配到左邊的邊數
    {
                //son存的是父節點到兒子的邊的編號
        ll sum=0,L=a[son[x][1]].to,R=a[son[x][2]].to;
        if(j-1>=0)//因為兒子到父節點也有條邊,所以j-1才是左節點保留的邊數
            sum+=a[son[x][1]].v;
        if(i-j-1>=0)//表示右邊有分配
            sum+=a[son[x][2]].v;
        if(j==0)//j==0表示只分配到了右邊
            dp[x][i]=max(dp[x][i],dp[R][i-1]+sum);
        else
            dp[x][i]=max(dp[x][i],dp[L][j-1]+dp[R][i-j-1]+sum);
        }

這樣就好了

程式碼

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}//好用的快讀 
ll n,q;
ll head[401],dp[401][30010],son[401][3];
struct ljj
{
    ll stb,to,v;
}a[601];
ll s=0;
inline void insert(ll x,ll y,ll z)
{
    s++;
    a[s].stb=head[x];
    a[s].to=y;
    a[s].v=z;
    head[x]=s;
}
inline void dfs(ll x,ll fa)
{
    ll tot=0;
    for(ll i=head[x];i;i=a[i].stb)
    {
        ll xx=a[i].to;
        if(xx==fa)
            continue;
        tot++;
        son[x][tot]=i;//son表示x到兒子的邊(son[x][1]表示左節點的表,son[x][2]表示右節點) 
        dfs(xx,x);
    }
    if(!tot)//如果x沒有節點,就返回 
        return;
    for(ll i=1;i<=q;i++)//列舉保留邊數
    for(ll j=0;j<=i;j++)//列舉分配到左邊的邊數
    {
                //son存的是父節點到兒子的邊的編號
        ll sum=0,L=a[son[x][1]].to,R=a[son[x][2]].to;
        if(j-1>=0)//因為兒子到父節點也有條邊,所以j-1才是左節點保留的邊數
            sum+=a[son[x][1]].v;
        if(i-j-1>=0)//表示右邊有分配
            sum+=a[son[x][2]].v;
        if(j==0)//j==0表示只分配到了右邊
            dp[x][i]=max(dp[x][i],dp[R][i-1]+sum);
        else
            dp[x][i]=max(dp[x][i],dp[L][j-1]+dp[R][i-j-1]+sum);
    }
}
int main()
{
    n=read();q=read();
    for(ll i=1;i<n;i++)
    {
        ll x=read(),y=read(),z=read();
        insert(x,y,z);
        insert(y,x,z);
    }
    dfs(1,0);
    printf("%lld\n",dp[1][q]);
}