背包整理模板

一 01背包

1.      hdu5230,1~n连续的数每个数只能用一次,构造出和为i(在1到n范围),本来dp是dp【】【】用到哪个数,变式,用的数最多少于sqrt(n),dp【】【】i个数,和为j,然后dp转移。

2.      一共20个人,每次9 3 1的打,每人hp上限60。本身不是背包,但是搞出用9和 3 和 1分别多少次来dp很好的描述结果,中间的转移枚举+限制就好。

3.      需要排序的背包。就是顺序调整后会更优,但是可以比较两个物品就贪心的确定放的顺序。还有贪心也可能是这样,比如最后5块钱什么都能买,那么把贵的放在后面。

4.      100个仓库,有守夜人,仓库之间一样,定义dp【i】为搞定了i个仓库。然后看每个人,缩小规模。

5.      01背包装压放物品时,用更新逆着扫很好写。有题目要3个状态,那么dp【i】【j】表面上是4个状态,但是不合法的状态不作为更新点,还是3个状态。

6.      注意缩小边界上限,比如三角形周长和1600,那么一定最多一个大于800,直接dp【800】【800】。

7.      注意用longlong来状压dp的值来表示构成信息。

8.      树形依赖背包,把信息一直传下去,并且写法规定第一次dfs(u)的时候再强制买根,传下去的时候先不买根。常用的方法.

voiddfs(int u) 

    for(int i = tot;i >= price[u];i--)//强制买根 

    { 

        if(dp[u][i - price[u]] != MIN) 

        { 

            dp[u][i] = dp[u][i - price[u]] +val[u]; 

        } 

        else 

        { 

            dp[u][i] = MIN; 

        } 

    } 

    for(int i = 0;i < price[u];i++) 

    { 

        dp[u][i] = MIN; 

    } 

    REP(i, 0, G[u].size())//遍历子树 

    { 

        int v = G[u][i]; 

       for(int j = 0;j <= tot;j++)//先拷过去 

       { 

           dp[v][j] = dp[u][j]; 

       } 

       dfs(v);//解决子树 

       for(int j = 0;j <= tot;j++)//取大 

       { 

           dp[u][j] = max(dp[u][j],dp[v][j]); 

       } 

    } 

9.      不一定dp后面的值是价值,比如说是概率啊,什么的,一个物品一个物品的考虑,都看成背包吧

10.  对于一些题目,当寻找闸值来搞dp,比如爱61,又比如部分贪心。

11.  注意01背包的花费一般是正的,但是如果是负的也一定要注意扫的方向,特判一下。

12.  01背包标准求第K大:

//dp[][]改变为花费小于等于i的花费,加上一维k大

intdp[1000 + 10][50], n, tot, k, cost[110], val[110];//dp中多一维,用来记录k

inta[50], b[50];

 

voidgetDp()

{

    for(int i = 0;i <= tot;i++)

    {

        dp[i][1] = 0;//初始化的不同,体积i之内能够达到的最大体积

        for(int j = 2;j <= k;j++)

        {

            dp[i][j] = -1;//第二三等等之后目前都是-1,因为不能够重复,根据题意

        }

    }

    for(int i = 1;i <= n;i++)

    {

        for(int j = tot;j >= 0;j--)//前两层和01背包正常的是一样的。

        {

            for(int m = 1;m <= k;m++)//需要k个一起来,因为分成了强制使用i和不使用i的情况。

            {

                a[m] = dp[j][m];

               if(j>=cost[i]&&dp[j-cost[i]][m]!=-1)

                    b[m] =dp[j-cost[i]][m]+val[i];

                else

                    b[m] = -1;

            }

            int a_i = 1, b_i = 1, c_i = 1;//把a和b两种情况合并成一个,能够保证a和b一定不会大于k 

            while(c_i<=k) 

            {  

                int l = a[a_i]; 

                int r = b[b_i]; 

                if(l == r)//相等时只能够算一个 

                { 

                    dp[j][c_i] = l; 

                    a_i++; 

                    b_i++; 

                } 

                else if(l < r) 

                { 

                    dp[j][c_i] = r; 

                    b_i++; 

                } 

                else 

                { 

                    dp[j][c_i] = l; 

                    a_i++; 

                } 

                c_i++; 

            } 

        }     

}

注意,如果是完全背包,倒着扫.

如果是多重背包,2进制01背包.但是不能够解决允许重复的k大.

13.  没有明确的给定物品,自己构造出物品后去dp.比如:10个东西,每一趟能够搞若干个东西,问最少的趟数.先把每一趟能够搞的物品预处理出来,价值是他们,花费是1,然后dp背包,只是花费用2进制搞而已.小物品根据问法搞出大物品.(其实也是思考最后一趟搞定哪些).

14.  Dp时候可能爆longlong,稍微测下边界数据,然后用两个longlong可能就ok了

15.  01背包,必须要买的东西,直接买,,选择要买的东西就是01背包,如果分组,组内至少选一个,将第一个强制买,组内只能买1个每次用上一组的更新,如果额外有免费卷的机会,增加一位描述是否用过免费卷.

16.  多少种方案数使得剩下的物品都放不下.  注意不是剩余的空间最少,而是剩下的物品放不下.那么去枚举那个物品放不下,这时候发现排序之后可以利用很多.从小到大枚举,那么划分成两部分,一部分必须选,越来越少,一部分随意选,越来越多,用01背包处理就好.然后该物品放不下有多少种呢?就是:tot-一定要用的范围在0到放不下这个物品之间的和.注意特殊情况,一个都不能放特判为没有…

17.  注意分组背包的处理.注意如果有花费为0的东西,那么不能滚动更新.

18. 一道很好的树形dp和背包问题:机器人搞资源.重点是定状态:dp[u][i]指在u中停留了i个机器人.这样的转移就不会重复了.之后:每个点都必须要走到,至少选一个,其次,倒着扫背包.再者:更新的时候0的附加值是2*l,一个机器人跑进去再跑出来,其余的都是j*l.


二 完全背包

1.      有使用东西个数的限制,加一维。Dp【i】【j】用了i个物品了。

2.      如果物品有特殊性质是连续的,比如容量【l,r】的都有,这些还都是无限的,那么有一个闸值之后的容量都可以满足。因为【kl,kr】和【(k+1)l,(k+1)r】有重合。

3.      部分贪心。保证能够产生%max的值的个数大于max后就找到闸值了。

4.      高于闸值抵消个数。结论是:当物品长度小于max时,如果两种达到长度为l(l要保证大于max*max,max*max来保证使用的物品个数大于max),那么一定有共同的一段可以消去。

三 多重背包

1.      首先一个思路转换:有很多个能力值,要求生出来的每一个个体和另一个个体相比都有一个值大,那么最多生几个个体。这种交错属性转换为属性和为sum/2的容量背包的组成总情况数,结果就是最好的。然后要对多重背包求容量C的情况数。

下面写出求法:主要是红色的dp

           void getDp()

{

    memset(dp, 0, sizeof(dp));

    now = 0;

    last = 1;

    dp[last][0] = 1;

    REP_D(i, 1, n)    {

        for(int j = 0;j <= tot;j++)

        {

            if(j < 1)

            {

                dp[now][j] = dp[last][j];

            }

            else if(j <= t[i])

            {

                dp[now][j] = dp[last][j] +dp[now][j-1];

            }

            else

            {

                dp[now][j]= dp[last][j] + dp[now][j-1] - dp[last][j-t[i] - 1];

            }

        }

        swap(now, last);

    }

}

 

 

2.      单调队列搞多重背包。其实就是一个变换

F[i][j] = max { F[i - 1] [j k * v[i] ] + k * w[i] }  (0 <= k <= m[i]) 

d = v[i]a = j/ db = j % d,即 j = a * d + bk替换a –k

变成了

F[i][j] = max { F[i - 1] [b + k * d] - k *w[i] } + a * w[i]   (a m[i] <= k <= a)

这样之后就可以很快的搞了

const intMAX_K = 1e4 + 100;

intq_front, q_tail;

structnode

{

    int no, number;

};

nodeq[MAX_K];

 

 

void pack(intdp[], int tot, int cost, int val, int num)

{

    for(int j = 0;j < cost;j++)

    {

        q_front = 1;

        q_tail = 0;

        int i = 0;

        for(int k = j;k <= tot;k = k + cost)

        {

            while(q_front <=q_tail&&q[q_front].no < i - num)

            {

                q_front++;

            }

            if(dp[k]!=-1)//当前的点能够作为更新的点

            {

                int tt = dp[k] - i*val;

                while(q_front<=q_tail&&q[q_tail].number<tt)//这个看情况

                {

                    q_tail--;

                }

                q_tail++;

                q[q_tail].number = tt;

                q[q_tail].no = i;

            }

            if(q_front<=q_tail)//出现没有合法情况的时候

            {

                dp[k] = q[q_front].number +i*val;

            }

            else

            {

                dp[k] = -1;

            }

            i++;

        }

    }

}

3.      有时如果可以用一个used多少来记录当前硬币用了几个,而只为能不能达到某个值,或者达到某个值用的硬币最多或最少,可以先按题目将硬币排序,然后used来记录当前最少用的当前的硬币数目.

4.      二进制多重背包

voidzeroOneP(int val, int cost)//cost是v,val是n,01背包的逆序更新 

    for(int j =tot;j >= cost;j--) 

    { 

        dp_buy[j] = min(dp_buy[j], dp_buy[j -cost] + val); 

    } 

voidgetDpBuy()//多重背包 

    fill(dp_buy, dp_buy + key + 1, INF); 

    dp_buy[0] = 0; 

    for(int i = 1;i <= n;i++) 

    { 

        int k = 1;//k先1 

        while(k < num[i])//小的时候 

        { 

            zeroOneP(k, k*val[i]); 

            num[i] -= k; 

            k *= 2; 

        } 

        zeroOneP(num[i], num[i]*val[i]);//剩下的都弄 

    } 

 

         

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值