背包九讲

来源:互联网 发布:用windows api编程pdf 编辑:程序博客网 时间:2024/05/22 11:59

详细的背包九讲:

http://www.cnblogs.com/jbelial/articles/2116074.html


01背包:

给你一个容量为V的背包,给你n给物品,每个物品的大小为c【i】,价值为v【i】且每个物品只能取一次,求背包能装的物品的总价值最大为为多少

状态转移方程: dp[i][v]= max(dp[i-1][v], dp[i-1][v- c[i]] + v[i])

在遍历v的时候逆序遍历可以节省空间


完全背包:

每个物品可以取无数次

状态转移方程:

dp[i][v]= max(dp[i-1][v-k*c[i]] + k*v[i]) k>=0 && k*c[i]<= j

在遍历v时从小到大遍历可节约空间和时间


多重背包:

可转化为01背包求解


二维费用背包:

一个物品有两种花费,两种花费都不越界且某种话费最小

状态转移方程:dp【i】【u】【v】= dp【i-1】【u- a【i】】【v-b【i】】+ w【i】

如果是01背包,那么逆序遍历两个花费节约空间即可,完全背包则顺序遍历节约空间和时间

其实二维背包只要在一维状态上面加一维即可,其他照旧

 

求第K优解的背包:

只需在dp[i][v]上加一维状态,dp[i][v][k] 表示dp[i][v]下面的第k优解,以01背包为例,因为dp[i][v]= max(dp[i-1][v], dp[i-1][v-a[i]]+ b[i]), 所以求第k优解,我们只需要把dp[i][v][1,2..k]和

dp[i-1][v-a[i]][1,2..k] + b[i] 的前k个数存到dp[i][v][1,2,...k]中即可  


hdu 1203 I NEED A OFFER!  (01背包)

这题就是一个裸的01背包,但是我方向我在写01背包时总是想当然把边界当成1,结果这题的边界是0。。。。

看来太久没写背包越来越差了。。。

代码:

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;#define maxn 11111double dp[maxn], b[maxn];int a[maxn];double Max(double x, double y){    return x> y? x: y;}int main(){        int n, m;        while(scanf("%d %d",&n,&m)!=EOF)        {            if(n== 0 && m== 0)                break;            for(int i= 1; i<= m; i++)                scanf("%d %lf",&a[i],&b[i]);            for(int i= 1; i<= m; i++)            {                for(int j= n; j>= 0; j--)                    if(j>= a[i]) // 感觉以后遍历的时候最好写成for(int j= n; j>= a[i]; j--) 这样既不用判断,还不用担心边界搞错                    {                        double t= 1.0- (1.0- dp[j-a[i]]) *(1.0- b[i]);                        dp[j]= Max(dp[j], t);                    }              }              printf("%0.1lf%%\n",dp[n]*100);        }        return 0;}


hdu 1171 Big Event  in HDU (01背包)

题意:

给你n种物品,每种物品的个数为ai,价值为bi,把所有的物品均分成两份使得两份的价值差尽量少,且第一份的价值大于或等于第二份的价值


我记得很久之前LSS问过我一个问题,给你n个正数,每个数可以取正或者取反,求这n个数之和离0最近的和为多少

当时我想了一下,觉得把这n个数从小到大排序一次即可,然后把这n个数分成两堆,设第一堆的和为A,第二堆和为B

则分的方法如下:

从最大的数开始遍历,

当A<= B 时,把当前遍历的数ai放到第一堆中,即A+= ai

否则B+= ai

当时帮他测了几组简单的数据发现対了,然后以为这种方法时是対的,这题和他问的问题其实是一样的,所以我最开始没用背包写,就用这种方法写的。。。

然后就一直WA, 然后我发现这种方法完全是错的

例如有七个物品,价值分别为4,4,4,3,3,3,3 时,这种方法分成两堆的结果为14, 11 但很明显这个的正确结果为12 12

这种题目的正确解法应该是01背包,设所有物品的总价值为sum,应该判断离sum/2 最近的合法价值为多少, 结果为sum-ans,ans

同理 LSS问我的那题就应该找出离数字总和的一半最近的合法和

PS:

如果是N个物品,两人轮流来选的话,就只需要再加一维选的数字的数目即可,然后再在dp[k][n/2]中选出离sum/2最近的k即可


hdu 1171 代码:

#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>using namespace std;#define maxt 511111int a[maxt];int dp[maxt];int main(){//freopen("in","r", stdin);//freopen("out1","w", stdout);    int n;    while(scanf("%d",&n)!=EOF && n>= 0)    {        int tot= 0;       int xx, yy, sum= 0;        for(int i= 1; i<= n; i++)        {            scanf("%d %d",&xx,&yy);            for(int j= 1; j<= yy; j++)                a[++tot]= xx;            sum+= xx* yy;        }   //     for(int i= 1; i<= tot; i++)     //       printf("%d %d\n",i, a[i]);        memset(dp, 0, sizeof dp );        dp[0]= 1;        for(int i= 1; i<= tot; i++)                for(int j= sum/2; j>= a[i]; j--)                    if(dp[j- a[i]])                        dp[j]= 1;        int ans= 0 ;        for(int i= sum/2; i>= 0; i--)            if(dp[i])            {                ans= i;                break;            }        printf("%d %d\n",sum- ans,  ans);    }    return 0;}





代码:



hdu 2159 FATE   (二维花费的完全背包)

状态转移方程: dp【i】【u】【v】= dp【i-1】【u- k*a【i】】【v-k*b【i】】+ k*w【i】  (k>= 0 && k*a[i]<=u && k*b[i]<= v)

同样这题也木有一下就AC,还错了几次,首先我没优化时间,直接就写了四重循环,因为这是个完全背包,顺序遍历两个花费即可减少一重循环,(这里TLE了一次)

后面改了之后WA 才发现我把两个花费搞反了。。


代码:

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;#define maxn 111int dp[maxn][maxn], a[maxn], b[maxn];int main(){    int n, m, k, s;   // freopen("in","r",stdin);   //freopen("out1","w", stdout);    while(scanf("%d %d %d %d",&n,&m,&k,&s)!=EOF)    {        for(int i= 1; i<= k; i++)            scanf("%d %d",&a[i], &b[i]);        memset(dp, 0, sizeof(dp));        for(int i= 1; i<= k; i++)        for(int j= 1; j<= m; j++)        for(int u= 1; u<= s; u++)        if(j>= b[i])                dp[j][u]= max( dp[j][u], dp[ j- b[i] ] [u-1] + a[i] );        int ans= -1;        for(int i= 1; i<= m; i++)        for(int j= 1; j<= s; j++)            if(dp[i][j]>= n)                ans=max(ans, m-i);           printf("%d\n",ans);    }    return 0;}



hdu 2110     Crisis of HDU     (求方案总数)

其实求方案总数的背包和求最大价值是差不多的,只需要改一下状态转移方程即可

dp[j][v]= sum(dp[j][v- k*a[i]])   (k*a[i]<= v)


代码:

#include <cstdio>#include <cstring>#include <cmath>#define maxn 1111#define maxm 11111#define  MOD 10000int dp[maxm], d[maxm];int a[maxn], b[maxn];int main(){//freopen("in", "r", stdin);//freopen("out1", "w", stdout);    int n;    while(scanf("%d",&n)!=EOF && n)    {        int ssum= 0;        for(int i= 1; i<= n; i++)        {            scanf("%d %d",&a[i],&b[i]);            ssum+= a[i]*b[i];        }        memset(dp, 0, sizeof dp);        memset(d, 0, sizeof d);        dp[0]= d[0]= 1;        for(int i= 1; i<= n; i++)        for(int j= ssum; j>= a[i]; j--)        for(int k= 1; k<= b[i]; k++)        if(j>= k*a[i] && d[j-k*a[i]])        {            dp[j]=  (dp[j]+ dp[ j- k*a[i] ]) %MOD;            d[j]= 1;        }       // printf("%d\n",ssum);        if(ssum%3== 0 && d[ssum/3])            printf("%d\n",dp[ssum/3]);          else          printf("sorry\n");    }    return 0;}



hdu 2639  Bone Collector II (求01背包的第k优解)

在对两个序列合并的时候注意把两个序列要拉出来,不然直接会出错,另外需注意这题的k优解在不同的选取物品,同样的价值的情况下算同解,所以我们在统计k个解的时候还需要把价值一样的解剔除出去

 

代码:

#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>using namespace std;int dp[1111][33];int w[111], c[111];int main(){int T;scanf("%d",&T);while(T--){int n, v, k;scanf("%d %d %d",&n,&v,&k);for(int i= 1; i<= n; i++)scanf("%d",&w[i]);for(int i= 1; i<= n; i++)scanf("%d",&c[i]);memset(dp, 0, sizeof dp);for(int i= 1; i<= n; i++)for(int j= v; j>= c[i]; j--){int A[33], B[33];memset(A, 0, sizeof A);memset(B, 0, sizeof B);for(int t= 1; t<= k; t++)A[t]= dp[j][t], B[t]= dp[j-c[i]][t] + w[i]; //将两个序列拉出来合并,直接合并会出错,因为dp[v][x]改变的时候,dp[v-1][x]也改变int a= 1, b= 1,u= 1;while((a<=k || b<= k)&& u<= k){if( (b> k) || (a<= k && A[a]> B[b])){if(A[a]!= dp[j][u-1])dp[j][u++]= A[a];a++;}else{if(B[b]!= dp[j][u-1])dp[j][u++]= B[b];b++;}}}printf("%d\n",dp[v][k]);}return 0;}


 

 

 

0 0
原创粉丝点击