树形dp专题练习

洛谷p1272
题意:给出一棵含有n个结点的树,求最少删除几条边可以得到一个大小为p的子树

题解:树形dp。dp[u][siz]为得到一个以u为根结点大小为siz的树最少需要删除的边数,那么最后结果就是dp[u][p]的最小值+(u是否等于整棵树的根,如果不是则需要+1用于砍掉与根的联系,否则不需要+1)

#include

using namespace std;
#define debug(x) cout<<#x<<" is "<
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

using namespace std;
#define debug(x) cout<<#x<<" is "<
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;
}

你可能感兴趣的:(树形dp)