您的位置:首页 > 其它

背包整理模板

2015-05-26 10:03 246 查看

一 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/ d,b = j % d,即 j
= a * d + b,k替换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]);//剩下的都弄

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: