P2014 选课 (依赖性背包,树上背包) U53204 【数据加强版】选课(后序遍历优化)

这道背包和普通的背包不一样,这题的物品是有依赖,即要选这个物品必须要先选它的前置物品才行,这就变成了依赖性背包,很明显这样会就产生森林,森林的的话不好处理,加个价值为0的虚拟节点0就可以组成一棵树,那就好处理多了.

dp[i][j][k]表示在i的子树里从前j棵子树里取若干个物品放入容量k的包里能得到的最大价值,一般的树形dp里,父节点的dp值都是儿子转移来的。

可以把每个子树视为一个泛化物品,一开始搜到u时,u子树的泛化物品只包括u物品,然后通过把vi子树的泛化物品求出来,然后依次和u节点的泛化物品合并(这个合并就是用的2个泛化物品合并的方法,先枚举k,包容量,然后枚举v,分配给包u的容量)就可以得到u子树的泛化物品。然后因为要选物品必须要先选它的前置物品才行,所以我们

对每个初始化 for(int k=1;k<=m+1;k++) dp[u][0][k]=val[u],这样子才能保证最后得到的u子树的泛化物品的dp[u][j](j>=1)选取了u物品。

原版:n*m^2          这篇论文有nm的泛化物品优化方法,但是我看不懂

#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<"     "<<x<<endl;
const int maxn=1e6+10;
int d[4][2]={1,0,-1,0,0,1,0,-1};
int dp[305][305][305],val[305],n,m;
bool vis[305];
vector<int>g[305];

void dfs(int u){
    for(int k=1;k<=m+1;k++) dp[u][0][k]=val[u];
    for(int i=0;i<g[u].size();i++){
        dfs(g[u][i]);
        for(int k=1;k<=m+1;k++){
            dp[u][i+1][k]=dp[u][i][k];
            for(int v=1;v<=k;v++)
                dp[u][i+1][k]=max(dp[u][i+1][k],dp[u][i][v]+dp[g[u][i]][(int)(g[g[u][i]].size())][k-v]);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int tmp;
        scanf("%d%d",&tmp,&(val[i]));
        if(tmp!=0)
            vis[i]=1,g[tmp].push_back(i);
    }
    for(int i=1;i<=n;i++)
        if(!vis[i])
            g[0].push_back(i);
    dfs(0);
    cout<<dp[0][(int)g[0].size()][m+1]<<endl;
    return 0;
}
v#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<"     "<<x<<endl;
const int maxn=1e6+10;
int d[4][2]={1,0,-1,0,0,1,0,-1};
int dp[305][305],val[305],n,m;
bool vis[305];
vector<int>g[305];

void dfs(int u){
    for(int k=1;k<=m+1;k++) dp[u][k]=val[u];
    for(int i=0;i<g[u].size();i++){
        dfs(g[u][i]);
        for(int k=m+1;k>=1;k--){
            for(int v=1;v<=k;v++)
                dp[u][k]=max(dp[u][k],dp[u][v]+dp[g[u][i]][k-v]);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int tmp;
        scanf("%d%d",&tmp,&(val[i]));
        if(tmp!=0)
            vis[i]=1,g[tmp].push_back(i);
    }
    for(int i=1;i<=n;i++)
        if(!vis[i])
            g[0].push_back(i);
    dfs(0);
    cout<<dp[0][m+1]<<endl;
    return 0;
}

加强版:

这题数据范围很大,用之前的方法过不了,用了后序遍历来优化,求出树的后序遍历之和,求这个遍历序列对应的物品做一次类似01背包的dp就好了,dp[i][j]表示从前i个物品选若干物品放容量j的包的最大价值,转移方程是dp[i][j]=max(dp[i-1][j-1]+wi,dp[i-sz[i]][j])

#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<"     "<<x<<endl;
const int maxn=1e5+10;
int d[4][2]= {1,0,-1,0,0,1,0,-1};
inline ll read() {
    ll s = 0,w = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') w = -1;
        ch = getchar();
    }
    while(isdigit(ch))
        s = s * 10 + ch - '0',ch = getchar();
    return s * w;
}
inline void write(ll x) {
    if(x < 0)
        putchar('-'), x = -x;
    if(x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
int n,m,val[maxn],afo[maxn],sz[maxn],cnt;
vector<int>g[maxn],dp[maxn];

void dfs(int now){
    sz[now]=1;
    for(int i=0;i<g[now].size();i++)
        dfs(g[now][i]),sz[now]+=sz[g[now][i]];
    afo[++cnt]=now;
}

int main()
{
    n=read(),m=read();
    for(int i=0; i<=n+1; i++) {
        dp[i].reserve(m+3);
        for (int j = 1; j <= m + 2; j++)
            dp[i].push_back(0);
    }
    for(int i=1; i<=n; i++)
    {
        int tmp;
        tmp=read(),val[i]=read();
        g[tmp].push_back(i);
    }
    dfs(0);
    for(int i=1;i<=cnt;i++){
        for(int j=1;j<=m+1;j++){
            dp[i][j]=max(dp[i-sz[afo[i]]][j],dp[i-1][j-1]+val[afo[i]]);
        }
    }
    write(dp[cnt][m+1]);
    puts("");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40642465/article/details/99692721