多叉树的背包问题

明天开学

问题模型一般是:

给定n个物品,有一个容积为V的背包,每个物品依赖一个物品,选择一个必须选择所依赖的物品,依赖关系构成一棵树,每个物品有体积,价值,最大化价值;

这种模型称作树形有依赖背包问题;

常见方法大概有三种:

1.多叉树转化为二叉树:

将一个节点的多个儿子中选择最左的儿子保留下来,剩下的儿子放到左儿子的右子树里;

来张图片:

状态转移即为:

设dp[i][j]表示在以i为根的子树中,用大小为j的包能取得的最大价值,

dp[i][j]=max(dp[left[i]][j-w[i]]+v[i],dp[right[i]][j]);

left[i]是i在原树中的第一个儿子,right[i]是i在原树中的下一个兄弟。

第二种方法:dfs序表示法:

我们对于每一个节点求出他在原树中的dfs序dfn值,然后记录他的子数大小size;

那么对于一个节点x:x的第一个儿子ydfn[y]=dfn[x]+1;

对于x的第二个儿子z,dfn[z]=dfn[x]+size[y]+1;

对于x的一个兄弟k,有dfn[k]=dfc[x]+size[x]+1;

一个dfs序为i的节点u,同样设dp[i][j]表示在以u为根的子树中,用大小为j的包能取得的最大价值,

dp[i][j]+w[i]->dp[i+1][j-v[i]]

dp[i][j]->dp[i+size[i]+1][j]

这也是较常用的刷表法,用当前状态更新后继的状态;

两种方法主要解决点权问题;

那么我们看一下用树形分组背包问题:

一道经典问题 选课:题目传送门;

题解(https://www.cnblogs.com/Tyouchie/p/10830072.html);

我们设dp[k][i][j]表示以i为根的子树,在前k个儿子中,选出一个大小为j的子树(必须包含i),所获得的最大学分。

那么我们每计算到第k+1个新的儿子v时(size[v]表示v的儿子个数),

dp[k+1][i][j]=min(dp[k][i][j-t]+dp[size[v]][v][t]);

k这一维显然可以无用的;

那么我们改为f[i][j]表示在以i为根的子树内选择了j门课程所获得的最大学分;

dp[i][j]=max(dp[i][j-t]+dp[v][t]);

j=m-1->1(m指容积,-1是因为当前点占据了1的体积);

t=1->j(在我那片题解中我倒序枚举了t,但是我发现两者都是正确的);

这个模型感觉要理解并且掌握,因为很多树形背包类dp的状态都是这样转移的;

#include<bits/stdc++.h>
using namespace std;
int lin[1000],tot,n,m,x,f[400][400],s[400];
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
    x*=f;
}
struct gg {
    int y,next;
}a[2000];
inline void add(int x,int y) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
} 
inline void dp(int x) {
    f[x][0]=0;
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        dp(y);
        for(int t=m;t>=0;--t) {
            for(int j=0;j<=t;++j) {
                if(t-j>=0) {
                    f[x][t]=max(f[x][t],f[x][t-j]+f[y][j]);
                }
            }
        }
    }
    if(x!=0) {
        for(int t=m;t>0;t--) 
            f[x][t]=f[x][t-1]+s[x];
    }
}
int main() {
    read(n);read(m);
    for(int i=1;i<=n;i++) {
        read(x);
        add(x,i);
        read(s[i]);
    }
    dp(0);
    cout<<f[0][m]<<endl;
    return 0;
}
View Code

重建道路;

f[i][j]表示以i为根的子树内分离一个大小为j的子树所需要的最少操作数;

j=p->1;

t=1->j;

和上一道题目很像;

显然f[x][1]为每个点x的度;

再转移的时候-2是因为u->v的边和v->u的边都已经在初始化的时候被减掉了,所以这时候要把他们连起来就得减去他们两个造成的贡献,也就是2.,思考一下,很好理解;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
const int N=250; 
struct gg {
    int y,next,v;
}a[N<<1];
int n,p,tot,x,y,lin[N],du[250],dp[250][250];
inline void add(int x,int y) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
}
inline void dfs(int x,int fa) {
    dp[x][1]=du[x];
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y==fa) continue;
        dfs(y,x);
        for(int j=p;j>=1;j--) {
            for(int t=1;t<=j;t++) {
                dp[x][j]=min(dp[x][j],dp[x][j-t]+dp[y][t]-2);
            }
        }
    }
}
int main() {
    read(n); read(p);
    for(int i=1;i<n;i++) {
        read(x); read(y);
        add(x,y);
        add(y,x);
        du[x]++; du[y]++;
    }
    memset(dp,0x3f,sizeof(dp));
    dfs(1,0);
    int ans=1<<30;
    for(int i=1;i<=n;i++) {
        ans=min(ans,dp[i][p]);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

有线电视网

真正理解前两到题目这道也就不难了;

我们设f[i][j]表示在以i为根的子树内,让j个人看所获得的最大收益;

那么对于每一个叶子节点x,f[x][1]就等于val[x];

那么最后我们寻找一个最大的i满足f[1][i]>=0;

所以我们倒序扫描就行了;

状态转移是j,t的枚举和前两题是一样的;

根据当前背包容积转移;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int n,m,dp[3010][3010],val[3010],k,x,v,tot,lin[3010];
struct gg {
    int y,next,v;
}a[1000100];
inline void add(int x,int y,int v) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
    a[tot].v=v;
}
int dfs(int u)
{
    if(u>n-m) {
        dp[u][1]=val[u];
        return 1;
    }  
    int sum=0,s,t;
    for(int i=lin[u];i;i=a[i].next) {
        int y=a[i].y;
        s=dfs(y);
        sum+=s;
        for(int j=sum;j>0;j--) {
            for(int t=1;t<=j;t++) {
                    dp[u][j]=max(dp[u][j],dp[u][j-t]+dp[y][t]-a[i].v);
            }
        }
    }
    return sum;
}
int main() {
    memset(dp,0xcf,sizeof(dp));
    read(n); read(m);
    for(int i=1;i<=n-m;i++) {
        read(k);
        for(int j=1;j<=k;j++) {
            read(x); read(v);
            add(i,x,v);
        }
    }
    for(int i=n-m+1;i<=n;i++) read(val[i]);
    for(int i=1;i<=n;i++) dp[i][0]=0;
    dfs(1);
    for(int i=m;i>=1;i--)
        if(dp[1][i]>=0) {
            printf("%d",i);
            break;
        }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Tyouchie/p/11395102.html