【解题报告】有依赖的背包问题

这里有一道非常典型的题目:
链接戳这里☞:

P1064金明的预算方案
下面是源代码:

#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
using namespace std;
struct sd{
    int  weight,value;
    bool prime;
}thing;
int  M[320005];
int num=0;
bool judger[320005];
vector<sd> subject[80];//subject是指刚刚进入分组时的物品
vector<sd> group[80];//进行后续分组背包时的所用的数组
int m,n;
void init();
int main()
{
    init();
    int limit=0;
    for(int i=1;i<=num;++i)
    {
        for(int k=limit;k>=0;--k)
        {
            for(int j=group[i].size()-1;j>=0;--j)
            {
                if((k==0||M[k]!=0)&&k+group[i][j].weight<=m)
                {
                    if(!judger[k]&&M[k+group[i][j].weight]<M[k]+group[i][j].value)
                    {
                        M[k+group[i][j].weight]=M[k]+group[i][j].value;
                        judger[k+group[i][j].weight]=true;
                    }
                }
                if(k+group[i][j].weight>limit)
                limit=k+group[i][j].weight;
            }
        }
        memset(judger,false,sizeof(judger));
    }
    long long  ans=0;
    for(int i=0;i<=m;++i)
    if(ans<M[i])
    ans=M[i];
    cout<<ans;
    return 0;
}
void init()
{
    int  a,b;
    int c;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        thing.value=a*b;
        thing.weight=a;
        if(c==0)
        {
            thing.prime=true;
            subject[i].push_back(thing);
        }
        else
        {
            thing.prime=false;
            subject[c].push_back(thing);
        }
    }
    for(int i=1;i<=n;++i)
    {
        if(subject[i].size()==0)
        continue;
        else
        {
            num++;
            if(subject[i].size()==1)
            {
                group[num].push_back(subject[i][0]);
            }
            else if(subject[i].size()==2)
            {
                int king;
                if(subject[i][0].prime) king=0; else king=1;
                group[num].push_back(subject[i][king]);
                sd change;
                change.weight=subject[i][king].weight+subject[i][1-king].weight;
                change.value=subject[i][king].value+subject[i][1-king].value;
                group[num].push_back(change);
            }
            else if(subject[i].size()==3)
            {
                int king;
                if(subject[i][0].prime) king=0; else if(subject[i][1].prime) king=1; else king=2;
                group[num].push_back(subject[i][king]);
                sd swp;
                swp=subject[i][king];
                subject[i][king]=subject[i][0];
                subject[i][0]=swp;
                sd change;
                change.weight=subject[i][0].weight+subject[i][1].weight;
                change.value=subject[i][0].value+subject[i][1].value;
                group[num].push_back(change);
                change.weight=subject[i][0].weight+subject[i][2].weight;
                change.value=subject[i][0].value+subject[i][2].value;
                group[num].push_back(change);
                change.weight=subject[i][0].weight+subject[i][1].weight+subject[i][2].weight;
                change.value=subject[i][0].value+subject[i][1].value+subject[i][2].value;
                group[num].push_back(change);
            }
        }
    }
}

其实说实话,有依赖的背包问题和分组背包问题没有什么太大的区别,最主要的区别就是我们在进行分组背包前要先进行一次01背包(但是在代码里我并没有这样实现,因为我嫌有一点麻烦,所以我用的强压的方式,把每一种方案枚举出来)。后面就和分组背包的思路是一样的了!

但是这里有一个分组背包以前没有讲到的东西······

下面重点来了!!!

Question:为什么分组背包的limit循环和后面的一个循环组内背包的循环不能反着写啊?
这个问题非常的important啊!我和我的同学谈论了一会儿,终于得出了答案!
为了理解的方便,这里我们采用画图的方式进行表达!

1、(错误情况)先循环物品,再循环背包

会出现的bug是,我们同组先扔进去的物品会对后面扔进去的物品产生影响,怎么说呢?见下图:

A:12-------扔入v=7------→B:18
()()()()()()()()()()()()()()
 |________________________↑
 						  ↑
						judger[B]=false

我们可以发现如果在A扔入7那么B点就为19>18,所以此时B点按常理就会自动更新然后把judger变为true,从而对他进行维护。于是此时就为下图:

A:12-------扔入v=7------→B:18
						  ↓judger[B]=true;
()()()()()()()()()()()()()()()()()()()()()()()()()()()()()
						   |____________________________↑
						   	本来是可以在这里扔入物品2
							   到达C点的,可是~~~ 
							    judger[B]=true		

于是我们成功的发现就这样,我们就少考虑了一种情况(注意:此情况应该是加上原来的B点位置的值,即judger一来没有被标记为true的时候),于是在这种少考虑的情况的情况下,很容易就WA掉了几个点。
2、正确情况为什么就是正确的呢?

因为第二种情况可以有效的避免出现少考虑情况的问题,在我们对一个背包中的点扔同一组的东西时,我们可以保证带有true标记的judger一定是在这个点后面的,所以不会对前面的点造成影响(相当于为了公平起见,我们这些点都是同时的扔入背包中同一个位置,如果先让一个点把背包扔一遍,实际上是很不公平的!)
下面放一些图方便理解:
错误的扔法:

step1:

A:12-------扔入v=7------→B:18
()()()()()()()()()()()()()()
 |________________________↑
 						  ↑
						judger[B]=false

step2:

A:12-------扔入v=7------→B:18
						  ↓judger[B]=true;
()()()()()()()()()()()()()()()()()()()()()()()()()()()()()
|————————————|—————————————|____________________________↑
			 △			   	本来是可以在这里扔入物品2
							   到达C点的,可是~~~ 
							    judger[B]=true	

相信大家在仔细看了这两幅图后,都应该明白了,有△的地方就存在被同一组前一个物品修改的可能!!!因为被修改了原来的值,所以judger变为true然后就少考虑情况了!!!而如果是正确的话(即for循环没有写反)那么△的地方就不会存在一来就被某个物品修改的情况,所以不会WA!!!

相信大家认真阅读后,应该都可以完全理解!
谢谢采纳!!!

原文地址:https://www.cnblogs.com/mudrobot/p/13330756.html