hdu 2191 多重背包基础题以及(单调队列O(vm))的用法

#include <stdio.h>
#include <string.h>
#define INF 1000000000
int max(int a,int b)
{
    return a>b?a:b;
}
int f[111];
int c[111],w[111],d[111];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m,i,j,k;
        scanf("%d%d",&m,&n);
        for(i=0;i<n;i++)
            scanf("%d%d%d",&c[i],&w[i],&d[i]);
        memset(f,0,sizeof(f));
        for(i=0;i<n;i++)
        {
            if(c[i]*d[i]<=m)
            {
                for(k=1;k<d[i];k=k*2)//多重转换成01背包
                {                       //举例1至24的数可以转换成1,2,4,8,9的任意组合
                    for(j=m;j>=c[i]*k;j--)//那么可以讲输入的p,h,24,转成p,h*1;p,h*2;p,h*4;p,h*8;p,h*9的01背包了
                        f[j]=max(f[j],f[j-c[i]*k]+w[i]*k);
                    d[i]-=k;
                }
                for(j=m;j>=c[i]*d[i];j--)
                    f[j]=max(f[j],f[j-c[i]*d[i]]+w[i]*d[i]);
            }
            else //总数超过m,可以看做完全背包
                for(j=c[i];j<=m;j++)
                    f[j]=max(f[j-c[i]]+w[i],f[j]);
        }
        printf("%d\n",f[m]);
    }
    return 0;
}

下面是套用单调队列的多重背包的代码,时间复杂度只有O(vn);个人理解可能有误,请自行分析

#include<stdio.h>
#include<string.h>
#define MAXN 111
struct Queue
{
    int num,value;
}que[MAXN];
int head,tail;
int dp[MAXN];
void enqueue (int x , int y)
{
    while (que[tail].value<y && head<=tail) tail--;//删除队列中比插入数小的值,确保队列从大到小
    que[++tail].num=x;
    que[tail].value=y;
}
int c[111],w[111],v[111];
int main()
{
    int i,j,d,n,m;

    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&m,&n);
        for (i =0; i < n ; ++i)
            scanf("%d%d%d",&v[i],&w[i],&c[i]);
        memset(dp,0,sizeof(dp));
        for (i=0 ; i<n ; ++i)
        {
            if (c[i] == 0) continue;
            if (c[i] > m/v[i]) c[i]=m/v[i];//c[i]*v[i]>m时就是完全背包,它的处理方式跟c[i]*v[i]=m时一样
            //总体积是10,当前体积是4,则dp顺序是{{0,4,8},{1,5,9},{2,6,10},{3,7}}
            for (d=0 ; d<v[i] ; ++d)
            {
                head=1;tail=0;
                for (j=0 ; j<=(m-d)/v[i] ; ++j)//j-1的dp下标正好是j的dp下标-v[i]
                {
                    enqueue(j , dp[j*v[i]+d]-j*w[i]);
                    while (que[head].num < j-c[i]) head++;//去掉最大值(如果最大值不是后面c+1个数中的数)
                    dp[j*v[i]+d]=que[head].value+j*w[i];
                }
            }
        }
        printf("%d\n",dp[m]);
    }
    return 0;
}
/*
dp公式是dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i])(0<=k<=c[i])
v为当前体积,w是当前价值,c是当前个数
由上式可以知道
c=2时,l<v
dp[4*v+l]=max(dp[4*v+l],dp[3*v+l]+w,dp[2*v+l]+2*w)
dp[5*v+l]=max(dp[5*v+l],dp[4*v+l]+w,dp[3*v+l]+2*w)
dp[6*v+l]=max(dp[6*v+l],dp[5*v+l]+w,dp[4*v+l]+2*w)
入队列时:(单调队列,保持第一个数是最大值)
dp[l],dp[l+v]-w,dp[l+2*v]-2*w,dp[l+3*v]-3w,dp[l+4v]-4w
用while (que[head].num < j-c[i]) head++;去掉最大值(如果最大值不是后面c+1个数中的数)
出队列,出最大值
dp[l+4v]=max(dp[l+2*v]-2*w,dp[l+3*v]-3w,dp[l+4*v]-4w)+4w=max(dp[4*v+l],dp[3*v+l]+w,dp[2*v+l]+2*w)
dp[l+5v]-5w入队列
出队列dp[l+5v]=max(dp[l+3*v]-3w,dp[l+4*v]-4w,dp[l+5*v]-5w)+5w=max(dp[5*v+l],dp[4*v+l]+w,dp[3*v+l]+2*w)
正好构成循环
依此类推,枚举l(0<=l<v)求出所有dp值
*/



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值