樹形dp專題練習

洛谷p1272
題意:給出一棵含有n個結點的樹,求最少刪除幾條邊可以得到一個大小爲p的子樹

題解:樹形dp。dp[u][siz]爲得到一個以u爲根結點大小爲siz的樹最少需要刪除的邊數,那麼最後結果就是dp[u][p]的最小值+(u是否等於整棵樹的根,如果不是則需要+1用於砍掉與根的聯繫,否則不需要+1)

#include<bits/stdc++.h>

using namespace std;
#define debug(x) cout<<#x<<" is "<<x<<endl;
typedef long long ll;
typedef unsigned long long ull;

const int maxn=155;
const int maxn2=2e4+5;
const ll inf=1e15;

int cnt,head[maxn],siz[maxn],dp[maxn][maxn],out[maxn];

struct edge{
    int fr;
    int to;
    int nex;
}e[maxn<<1];

void adde(int x,int y){
    e[cnt].fr=x;
    e[cnt].to=y;
    e[cnt].nex=head[x];
    head[x]=cnt++;
}

void dfs(int u,int f){
    siz[u]=1;
    for(int i=head[u];i!=-1;i=e[i].nex){
        int v=e[i].to;
        if(v==f)continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
    dp[u][siz[u]]=0;
}

void dfs2(int u,int f){
    dp[u][1]=out[u];
    for(int i=head[u];i!=-1;i=e[i].nex){
        int v=e[i].to;
        if(v==f)continue;
        dfs2(v,u);
        for(int j=siz[u]-1;j>=1;j--){
            for(int k=min(j-1,siz[v]);k>=1;k--){
                dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1);
               /* if(u==1&&j==1&&dp[u][j]==3){
                    debug(k);
                    debug(v);
                    debug(dp[u][j-k]);
                    debug(dp[v][k]);
                }*/
            }
        }
    }
}

int main(){
    int n,p;
    scanf("%d%d",&n,&p);
    memset(head,-1,sizeof(head));
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        adde(a,b);
        adde(b,a);
        out[a]++;
    }
    dfs(1,0);
    dfs2(1,0);
    int ans=0x3f3f3f3f;
    for(int i=1;i<=n;i++){
        if(i==1){
            ans=min(ans,dp[i][p]);
        }
        else{
            ans=min(ans,dp[i][p]+1);
        }
       // debug(i);
       // debug(dp[i][p]);
    }
    printf("%d\n",ans);
    return 0;
}


洛谷p1273
題意:給出一棵以1爲根的含有n個結點的樹,樹的邊權值爲非正數,樹的葉子結點權值爲非負數,其餘結點權值爲0,求包含根節點1且點權值+邊權值爲非負數的子樹最多包含的葉子結點的個數

題解:樹形dp。dp[u][num]爲以u爲根節點包含num個葉子結點的樹的點權值+邊權值的最大值,則顯然可以推出樹與其子樹之間的轉移方程,最後找到dp[1][num]爲非負數的最大num即可

#include<bits/stdc++.h>

using namespace std;
#define debug(x) cout<<#x<<" is "<<x<<endl;
typedef long long ll;
typedef unsigned long long ull;

const int maxn=3e3+5;
const int maxn2=2e4+5;
const ll inf=1e15;

int cnt,head[maxn],num[maxn];
ll val[maxn],dp[maxn][maxn];

struct edge{
    int fr;
    int to;
    int nex;
    ll val;
}e[maxn];

void adde(int x,int y,int z){
    e[cnt].fr=x;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].nex=head[x];
    head[x]=cnt++;
}

void dfs(int u,int f){
    int cntt=0;
    for(int i=head[u];i!=-1;i=e[i].nex){
        int v=e[i].to;
        if(v==f)continue;
        dfs(v,u);
        cntt++;
        num[u]+=num[v];
        for(int j=num[u];j>=1;j--){
            for(int k=min(j,num[v]);k>=1;k--){
                dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].val);
            }
        }
    }
    if(!cntt){
        dp[u][1]=val[u];
        num[u]=1;
    }
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
   // memset(dp,-1,sizeof(dp));
    for(int i=1;i<=n-m;i++){
        int k;
        scanf("%d",&k);
        for(int j=1;j<=k;j++){
            int a;
            ll b;
            scanf("%d%lld",&a,&b);
            adde(i,a,b);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            dp[i][j]=-inf;
        }
    }
    for(int i=n-m+1;i<=n;i++)scanf("%lld",&val[i]);
    dfs(1,0);
    int ac=0;
    for(int i=num[1];i>=0;i--){
       // debug(dp[1][i]);
        if(dp[1][i]>=0){
            ac=i;
            break;
        }
    }
    printf("%d\n",ac);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章