背包九讲

详细的背包九讲:

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 11111

double 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 511111

int 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 111

int 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 10000

int 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;
}


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值