打牌第二天之01背包

HDU 2602 Bone Collector

题意:给n个物品和一个体积为w的背包,每个物品的体积和价值已知,问这个包最多能装多少价值的物品

分析;对于每个物品来说,有两种状态,装or不装。直接暴力?来看看数据范围,n<=1000,gg….如果n在25左右,还可以考虑一下。

那我们考虑一下打牌~
如果状态是dp[i][j]:前i个物品,拿j个。
那么答案就是max(dp[n])
首先看三个条件,符合.
但是,好像漏了些啥。体积!对了还有体积这个限制条件。那么还需要把当前状态的体积记录下来呀,咋记录,因为dp[i][j]这一个状态的体积可能不止一种呀。。

那么换一个想法,物品的个数我们不需要知道,我们就用体积来转移
状态为 dp[i][v]:前i个物品 体积为v时的最大价值
状态转移方程:
if(v-w[i]>=0)
dp[i][j]=max(dp[i-1][j],dp[i-1][v-w[i]]);
else dp[i][j]=dp[i-1][j];

时间 n*m,空间n*m。能不能改良一下?时间好像改不了,空间好像可以。第i个物品的状态是由第i-1个物品的状态转移过来的,可以用滚动数组,空间为n*2。能不能只用一维呢?行。d[i][j]只能由dp[i-1][j]和dp[i-1][v-w[i]]转移过来,j和j-w[i]都不大于j,所以在求出了前i-1个物品所有状态后,求前i个物品的时候,从后开始转移。
dp[i]:体积为i的最大价值

for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--)
        {
            if(j>=w[i])
                dp[j]=max(dp[j],dp[j-w[i]]);
        }
    }

饭卡 HDU - 2546

题意:
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。

和前面的问题好像啊~直接出牌
状态为 dp[i][j]:前i种菜,卡中的钱为j,最后卡中最少的余额是多少
ans=dp[n][v];
状态转移

for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=mon;j++)
        {
            if(dp[i-1][j]>=5)
            {
                if(j>=m[i])
                    dp[i][j]=min(dp[i-1][j],dp[i-1][j-m[i]]);
                else dp[i][j]=dp[i-1][j]-m[i];
            }
            else dp[i][j]=dp[i-1][j]
        }
    }

这样状态转移对么?
如果样例是
2个菜,菜的价格分别是3 100,饭卡中的余额为5
那么按照上面的状态转移
dp[1][5]=2,其他dp[1]都为0
dp[2][5]=2;
可是这里先买100,是最优解呀。。为什么会出现这种情况呢?
因为这个方程,只买第i种物品应该只与j有关,加个
if(j>=5) dp[i][j]=min(j-p[i],dp[i][j]);

for(int j=0;j<=v;j++) dp[0][j]=j;
    for(int i=1;i<=n;i++)       
    {
         for(int j=0;j<=v;j++)
         {
             if(dp[i-1][j]>=5)
             {
                 if(j>=p[i])
                     dp[i][j]=min(dp[i][j],min(dp[i-1][j],dp[i-1][j-p[i]]));
                 else dp[i][j]=min(dp[i][j],dp[i-1][j]-p[i]);
             }
             else dp[i][j]=dp[i-1][j];
             if(j>=5)
                 dp[i][j]=min(dp[i-1][j],j-p[i]);
         }
    }

还是wa,为啥?
dp[i][j]的状态会由
dp[i-1][j](第i种菜不取),
if(dp[i-1][j]>==5)
dp[i-1][j-p[i]](取第i种菜,第i种菜完全买下来),dp[i-1][j]-p[i](取第i种菜,第i种菜不完全买下来)
if( z)
j-p[i](只取第i种菜)转移过来
哪里错了呢。。。
来一组样例
3
2 1 11
6
程序运行出来的是-5;
因为dp[i-1][6]如果小于5,那么之后卡钱为6的状态只能由dp[i-1][6]和6-p[i]转移。
像这样之后还有限制的,就先把限制去掉后再来打牌…

如果大于或等于5,就先把5留下去买最贵的。其他就用01背包来写
01背包的时候和顺序是没有关系的啊~所以排不排序无所谓,但是最贵的要去掉,排完序后可以直接去掉最后一个。

用一个不排序的(来表示排序不重要:>

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

using namespace std;
const int maxn = 1004;
int p[maxn];
int dp[maxn][maxn];
int main()
{
    int n,v;
     while(scanf("%d",&n)!=EOF&&n){
            memset(p,0,sizeof(p));
            int maxx=0,k=0;
            for(int i=1;i<=n;i++){
                scanf("%d",&p[i]);
                if(p[i]>maxx) {maxx=p[i];k=i;}
            }
            for(int i=k;i<=n;i++)
                p[i]=p[i+1];
            scanf("%d",&v);
            memset(dp,0,sizeof(dp));
            if(v<5) printf("%d\n",v);
            else {
                v-=5;
                for(int i=1;i<=n-1;i++)
                {
                    for(int j=0;j<=v;j++)
                    {
                        dp[i][j]=dp[i-1][j];
                        if(j>=p[i])
                        dp[i][j]=max(dp[i][j],dp[i-1][j-p[i]]+p[i]);
                    }
                }
                printf("%d\n",v-dp[n-1][v]+5-maxx);
            }
    }
    return 0;
}

hdu 1171

题意,有一堆物品,给你每种物品的个数和价值,要求尽可能将这些物品分成A B 两堆价值相等,然后不等的话,A要比B大些。输出A,B价值

分析:直接打牌
01背包模板题嘛,背包体积为总价值/2,算出来是B的,用总-B的为A的.
完。

Codeforces 864E
题意:给n个物品,每个物品有三个值,t,d,v(t:保存这个物品需要的时间,d:在d时刻之前保存这个物品可以获得v价值,否则没有价值),问保存哪些,可获得最多价值

分析:直接出牌。可是这个和一般的01背包有点不一样,每个物品都有限制。
状态 dp[i][t]:前i个物品时间为t的时候,最大价值
那么按照d排个序,然后再01背包
这里需要输出路径
可以这样,但是要注意,这里的r和c是第一次出现的那个

    while(r&&c)
    {
        if(c-p[r].t>=0&&dp[r][c]==dp[r-1][c-p[r].t]+p[r].v)
        {
            ans[h++]=r;
            c=c-p[r].t;
        }
        r--;
    }

打牌过程: (代码中会有很多细节啊~

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

using namespace std;
const int maxn = 114;
struct node
{
    int t,d,v,id;
}p[maxn];

bool cmp(node a1,node a2)
{
    if(a1.d!=a2.d)
        return a1.d<a2.d;
    else return a1.v>a2.v;
}
int dp[maxn][2105],ans[maxn];

int main()
{
    int n,maxx=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d %d %d",&p[i].t,&p[i].d,&p[i].v);
        p[i].id=i;
        maxx=max(maxx,p[i].d);}
    sort(p+1,p+n+1,cmp);
    memset(dp,0,sizeof(dp));
    int total=-1;//这里total要初始化为-1,不能为0,否则会RE,因为有为0的情况。
    int r=0,c=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=maxx;j++)//在[0,p[i].t)区间中,dp[i][j]=dp[i-1][j]
            dp[i][j]=dp[i-1][j];
        for(int j=p[i].t;j<p[i].d;j++)
            dp[i][j]=max(dp[i][j],p[i].v+dp[i-1][j-p[i].t]);
        for(int j=p[i].d;j<=maxx;j++)
            dp[i][j]=dp[i][j-1];
    }
    for(int i=0;i<=maxx;i++)//r,c是第一次出现的,也就是恰好为
    {
        if(dp[n][i]>total)
        {
            c=i;
            total=dp[n][i];
        }
    }
    int h=0;
    r=n;
    while(r&&c)//寻找路径
    {
        if(c-p[r].t>=0&&dp[r][c]==dp[r-1][c-p[r].t]+p[r].v)
        {
            ans[h++]=r;
            c=c-p[r].t;
        }
        r--;
    }
    sort(ans,ans+h);
    for(int i=0;i<h;i++)
        ans[i]=p[ans[i]].id;
    printf("%d\n",dp[n][p[n].d-1]);
    cout<<h<<endl;
    for(int i=0;i<h;i++)
    {
        if(i!=0) printf(" ");
        printf("%d",ans[i]);
    }
    printf("\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值