依赖背包

依赖背包

问题来源:P1064 金明的预算方案(NOIP2006)
背包中的物品存在依赖关系,也就是说,如果选i物品,就必须选择j物品,j为主件,i为附件

分析

按照背包的一般思路,我们可以考虑对所有的主件进行dp选择,然后在选择每一个主件的时候再枚举附件,当然这个题只是每个主件最多只有2个附件,所以这就只有5个策略,但是如果我们推广到n呢,策略则有2n+1之多,为指数级。

我们在上边的基础上,我们可以做一些优化
将每一个主件和他的附件组成一个物品组,如果我们将每个物品组的对应的不同背包的最优解求出来,然后在对所有的物品组进行求背包问题,答案就求出来了。

那么我们怎么求每个物品组的对应的不同背包的最优解,显而易见,这也是一个背包问题,但是这个背包中如果有物品则至少有这个物品组的主件

一般化:这个题附件不能再有附件,但是如果附件再有附件呢?显然我们同样也可以创建个2级物品组 3级物品组……,对每个附件集进行01背包求解,逐层推之;

代码实现

代码一:这是枚举的代码,对每个主件的附件进行枚举。下边的代码用的记忆化搜索,其实完全可以改成动态规划求解QWQ(因为每个附件集最多2个附件,所以最多有5种策略,完全可以用5个if语句求解)
另外对于这个题来说,因为题目已有了限定,显然枚举更加快速简便,(上述的算法只能用于多附件问题)

#include<iostream>
#include<vector>
using namespace std;
vector<int>s[61];//存储子节点
int v[61];//价格
int r[61];//等级
int f[100];//根节点
int n,t;
int ans=0;
int dp[61][1000];
int dfs(int i,int t)
{
    if(t<=0)
        return 0;
    if(i==-1)
        return 0;
    if(dp[i][t]>0)
        return dp[i][t];
    int sum,w;
    int y=0;
    if(t-v[f[i]]>=0)
    y = dfs(i-1,t-v[f[i]])+v[f[i]]*r[f[i]];
    for(int j=0;j<(int)(s[f[i]].size());j++)
    {
        sum = v[f[i]],w = v[f[i]]*r[f[i]];
        if(sum<=t)
        {
             for(int h=j;h<(int)s[f[i]].size();h++)
             {
                 int k=s[f[i]][h];
                 sum+=v[k];
                 w+=v[k]*r[k];
                 if(sum<=t)
                 y = max(y,dfs(i-1,t-sum)+w);
                 else
                    break;
             }
        }
    }
    y = max(y,dfs(i-1,t));
    return dp[i][t]=y;
}
int main()
{
    cin>>t>>n;
    int q;
    for(int i=0;i<n;i++)
    {
        cin>>v[i]>>r[i]>>q;
        if(q==0)
        f[ans++]=i;
        else
            s[q-1].push_back(i);
    }
    cout<<dfs(ans-1,t);
    return 0;
}

代码二:(下边的代码在洛谷上只能过一半QWQ,其他都是超限,但对于多附件的题目来说,下面的算法更加的实用)

#include<iostream>
#include<vector>
using namespace std;
int dp[32001];//主件DP
int dp2[32001];//附件DP2
int v[70];//物品的价值
vector <int>fu[62];
int f[62];
int r[70];
int main()
{
    int ans=0;
    int n,m;//n钱 ,m个物品
    int a,c;
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>a>>r[i]>>c;
        v[i] = a;
        if(c==0)//主件
            f[ans++] = i;
        else//附件
            fu[c].push_back(i);//c的附件有i
    }
    for(int i=0;i<ans;i++)//对每个主件的附件进行dp
    {


        //初始化
        int k = f[i];//主件号
        int w =v[k]*r[k];
        if(fu[k].size()>0)
        {
            for(int t = v[k];t<=n;t++)
                dp2[t] = w;
        }//在背包足够大的情况下,一定选择主件


        for(int j=0;j<fu[k].size();j++)//第j个附件
        {
            int t = fu[k][j];//附件号
            w = v[t]*r[t];
            for(int h=n;h>=v[t];h--)//容量(价格)
            {
                if(h-v[t]>=v[k])//如果减去附件的价格后的价格够主件
                dp2[h] = max(dp2[h],dp2[h-v[t]]+w);
            }
        }


        for(int j=n;j>=v[k];j--)
        {
            if(fu[k].size()>0)
            {
                for(int h = j;h>=v[k];h--)
                    dp[j] = max(dp[j],dp[j-h]+dp2[h]);
            }
            else
                dp[j]= max(dp[j],dp[j-v[k]]+v[k]*r[k]);
        }
    }

    cout<<dp[n];
    return 0;
}

参考: https://www.luogu.org/discuss/show/47533

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值