训练第三周之dp-背包训练

01背包

特点:每种物品最多可放一次

样题:

题目:有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

分析:每种物品都有两种状态,放或者不放,那么只要比较放所得的价值与不放背包内原有价值就能得到最大价值

二维数组:f[i][j]表示前i件物品放入容量为j的背包可以获得的最大价值

for i=1...n
    for j=0...c
        if j>=w[i]
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
//不放价值为dp[i][j]=dp[i-1][j],前i-1件物品放入容量为j的背包所获价值
//放则dp[i][j]=dp[i-1][j-w[i]]+v[i],前i-1件物品放入容量为j-w[i]的背包的最大价值加上第i件物品的价值v[i]

一维数组:f[j]表示容量为j的背包可以获得的最大价值

for i=1..N
    for j=V..0/*倒序是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[j-w[i]],所以才逆推过去的*/
        f[j]=max{f[j],f[j-w[i]]+v[i]};

例题:

1、Bone Collector-hdu2602

训练第三周之dp-背包训练_第1张图片

思路:每种物品只有放和不放两种状态,不放则背包内价值为f[i-1][j],放了则为f[i-1][j-w[i]]+v[i],意思是前i-1件物品放入容量为j-w[i]的背包的最大价值加上第i件物品的价值v[i],取两者最大值为最大价值

代码:

#include
#include
#include
#include
using namespace std;
int v[1005],w[1005];//v为价值,w为体积
int dp[1005][1005];
int main(){
    int t,n,V,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&V);
        for(i=1;i<=n;i++)
            scanf("%d",&v[i]);
        for(i=1;i<=n;i++)
            scanf("%d",&w[i]);
        for(j=0;j<=V;j++)
            dp[0][j]=0;
        for(i=1;i<=n;i++)
        {
            for(j=0;j<=V;j++)
            {
                if(j>=w[i])
                {
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
                    //dp[i][j]指前i件物品放入容量为j的背包的最大价值
                }
                else
                    dp[i][j]=dp[i-1][j];
               // printf("%d ",dp[i][j]);
            }
            //printf("\n");
        }
        printf("%d\n",dp[n][V]);
    }
}

2、饭卡-hdu2546

思路:这个题目稍有不同,显然当余额为5的时候去买那最大价值的物品才能使卡内余额最少,我们不妨直接先拿出5块,也去掉最大价值的物品,这样就是简单的01背包了,这里我采用了一维数组,f[j]为容量为j的背包所能放入的最大价值

代码:

#include
#include
#include
#include
using namespace std;
int price[1005],f[1005];
int main()
{
    int n,j,i,m,Max;
    while(scanf("%d",&n)!=EOF&&n)
    {
        memset(price,0,sizeof(price));
        memset(f,0,sizeof(f));
        for(i=1;i<=n;i++)
            scanf("%d",&price[i]);
        scanf("%d",&m);
        if(m<5){
            printf("%d\n",m);
            continue;
        }
        sort(price,price+n+1);//排序是为了找出最大价值的物品
        Max=price[n];

        for(i=1;ifor(j=m-5;j>=price[i];j--)//j=m-5是最后留下5块买那个最大价值的物品
            {
                f[j]=max(f[j],f[j-price[i]]+price[i]);
                //f[j]表示余额为j的卡能买到的最大价值
            }
            //printf("%d\n",f[j]);
        }
        printf("%d\n",m-Max-f[m-5]);
    }
}

完全背包:

特点:每种物品都可以放任意次

样题:

题目:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

伪代码:

for i=1..N
    for j=0..V/*与01背包相比只是j的顺序不同而已,是因为完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,正需要一个可能已选入第i种物品的子结果f[j-w[i]],所以顺推过去*/
        f[j]=max{f[j],f[j-w[i]]+c[i]}

例题:

1、Piggy-Bank-hdu1114

训练第三周之dp-背包训练_第2张图片

代码:

#include
#include
#include
#include
using namespace std;
int p[50005],w[10005];
int f[10005];
int main()
{
    int t,e,g,n,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&e,&g);
        scanf("%d",&n);
        for(i=1;i<=n;i++)
            scanf("%d%d",&p[i],&w[i]);
        f[0]=0;
        for(i=1;i<=g-e;i++)
            f[i]=2e9;
        for(i=1;i<=n;i++)
        {
            for(j=0;j<=g-e;j++)
            {
                if(j>=w[i])
                    f[j]=min(f[j],f[j-w[i]]+p[i]);
            }
        }
        if(f[g-e]>=2e9)
            printf("This is impossible.\n");
        else
            printf("The minimum amount of money in the piggy-bank is %d.\n",f[g-e]);
    }
}

多重背包:

特点:每种物品最多取c[i]件

样题:

题目:有N种物品和一个容量为V的背包。第i种物品最多有c[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

分析:这个就相当于01背包和完全背包的综合,不止取一件,也取不了任意件。所以我们可以用二进制思想将其转化为01背包问题

方法:将第i件物品用二进制转化为若干件物品,每件物品有一个系数,系数为1,2,4,…,2^(k-1),c[i]-2^k+1,且k是满足c[i]-2^k+1>0的最大整数,因为2^k-1为前k-1的和。如13,转化为1,2,4,6,那么就把第i件分成了k件,物品的费用和价值均是原来的费用和价值乘以这个系数

核心代码:

for(i=1;i<=m;i++)//m为物品种类
        {
            for(k=1;k*2-1<=c[i];k=k*2)//将第i件分成若干件,系数为k,不包含最后一件的系数
                for(j=n;j>=p[i]*k;j--)//01背包问题
                {
                    f[j]=max(f[j],f[j-p[i]*k]+w[i]*k);

                }
            for(j=n;j>=p[i]*(c[i]-k+1);j--)//若干件中最后一件的系数为c[i]-k+1
                f[j]=max(f[j],f[j-p[i]*(c[i]-k+1)]+w[i]*(c[i]-k+1));
            //printf("%d\n",f[j]);
        }

1、悼念512汶川大地震遇难同胞——珍惜现在,感恩生活-hdu2191

代码:

#include
#include
#include
#include
using namespace std;
int p[105],w[105],c[105];
int f[105];
int main()
{
    int t,m,n,i,j,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(i=1;i<=m;i++)
            scanf("%d%d%d",&p[i],&w[i],&c[i]);
        memset(f,0,sizeof(f));
        for(i=1;i<=m;i++)
        {
            for(k=1;k*2-1<=c[i];k=k*2)
                for(j=n;j>=p[i]*k;j--)
                {
                    f[j]=max(f[j],f[j-p[i]*k]+w[i]*k);

                }
            for(j=n;j>=p[i]*(c[i]-k+1);j--)
                f[j]=max(f[j],f[j-p[i]*(c[i]-k+1)]+w[i]*(c[i]-k+1));
            //printf("%d\n",f[j]);
        }
        while(n--)
            if(f[n+1]){
                printf("%d\n",f[n+1]);
                break;
            }
    }
}

2、Dividing-hdu1059

代码:

#include
#include
#include
#include
using namespace std;
int c[10],s=1;
int f[200005];
int main()
{
    do
    {
        int flag=0,sum=0,i,k,v,j;
        for(i=1;i<=6;i++){
            scanf("%d",&c[i]);
            if(c[i]!=0)
                flag=1;
        }
        if(flag){
            sum=c[1]*1+c[2]*2+c[3]*3+c[4]*4+c[5]*5+c[6]*6;
            memset(f,0,sizeof(f));
            if(sum%2)
            {
                printf("Collection #%d:\n",s);
                printf("Can't be divided.\n\n");
            }
            else{
                v=sum/2; //比较特殊,因为看是否能平分,所以只需考虑一半
                for(i=1;i<=6;i++)
                {
                    for(k=1;k*2-1<=c[i];k=k*2)
                        for(j=v;j>=i*k;j--){
                            f[j]=max(f[j],f[j-i*k]+i*k);
                            //printf("%d %d\n",j,f[j]);
                        }
                    //printf("\n");
                    for(j=v;j>=i*(c[i]-k+1);j--){
                        f[j]=max(f[j],f[j-i*(c[i]-k+1)]+i*(c[i]-k+1));
                        //printf("%d %d\n",j,f[j]);
                    }

                }
                if(f[v]==v)
                {
                    printf("Collection #%d:\n",s);
                    printf("Can be divided.\n\n");
                }
                else
                {
                     printf("Collection #%d:\n",s);
                     printf("Can't be divided.\n\n");
                }
            }
        }
        else
            break;
    }while(s++);
    return 0;
}

你可能感兴趣的:(acm集训日志,背包问题)