poj 2184 背包变形

/*

        有关这个问题画了太多时间了;

        对自己真的很失望,思维能力太差了;

        庆幸的是自己坚持看懂了;

        希望下次碰到能够灵活运用;

*/

题目其实可以转换成背包问题;

由于数据是-100000~100000;

所以我们取数组 dp[100000*2];

我们产生一个相对坐标的概念,100000相对坐标为0;99995相对坐标为-5;x的相对坐标为x-100000;

由于每组数据的属性都为价值;

而对于背包的容量是无限扩展的,(题目要求的就是 背包最大能装多少价值的东西)

我们对此做个变形,对于smartness (S),funness(F),将S总和作为背包的容量(可扩展),将F总和作为背包物品的价值;

每考虑一个物品(s,f)的加入与否,如果假如则背包容量也相应增加s;

于是状态转移方程为

dp[i+MAX+s] = max(dp[i+MAX+s] , dp[i+MAX]+f);  //MAX = 100000


当然对于s>0和s<0时进行动态规划时,for循环的顺序要注意;

当s>0时;for循环要从_max 到 _min

当s<0时;for循环要从 _min到 _max

其道理和我们做01背包时候第二个for循环逆序的道理一样;

s<0时,更新的是相对目前这些数据的左边的数据,所以我们应该从_max-->_min

s>0时,更新的是相对目前这些数据的右边的数据,所以我们应该从 _min-->_max

细细体会~~~


//Problem: 2184		User: fuqiang
//Memory: 1680K		Time: 63MS
//Language: G++		Result: Accepted

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define MAX 100000
#define INF 999999999

bool visit[MAX*2+5];
int dp[MAX*2+5];  //dp[i+MAX]总的s值为i时的最优的f值(其实就是说两个都是和的形式,因为选了f就选了那个s)

int main()
{
    memset(visit,0,sizeof(visit));
    for(int i = 0; i < MAX*2+5; i++)
        dp[i] = -INF;
    dp[0+MAX] = 0;       //初始化,一组数据都没取的时候,结果为0;
    visit[0+MAX] = 1;    //初始化,已有结果为0,标记为1;这个0相当于0+MAX
    int T,_max = 0,_min = 0;   //_max , _min  为dp范围,范围-MID~MID
    scanf("%d",&T);
    int s,f;
    while(T--)
    {
        scanf("%d%d",&s,&f);
        if(s<=0&&f<=0)  //剪支;只有一个小于或等于0不能被剪
            continue;
        if(s>0)
        {
            for(int i = _max; i >= _min; i--)     //如果s>0则从大到小,否则从小到大扫描,会产生后效性(重复计算)
            {
                if(visit[i+MAX])  //如果vis[MID+i]已经标记为1,则此次策略会影响dp[MID+i];否则不影响
                if(dp[i+MAX+s]<dp[i+MAX]+f)  //表示该Cow可取
                {
                    dp[i+MAX+s] = dp[i+MAX]+f;
                    visit[i+MAX+s] = 1;
                }
            }
            _max += s;
        }
        else
        {
            for(int i = _min; i <= _max; i++)    //如果s<0则从小到大,否则从大到小扫描,会产生后效性(重复计算)
            {
                if(visit[i+MAX])
                if(dp[i+MAX+s]<dp[i+MAX]+f)
                {
                    dp[i+MAX+s] = dp[i+MAX]+f;
                    visit[i+MAX+s] = 1;
                }
            }
            _min += s;
        }
    }

    int ans = 0;
    for(int i = 0; i <= _max; i++)    //剪掉了i<0的情况
    {
        if(visit[i+MAX])
        if(dp[i+MAX]>0 && dp[i+MAX]+i > ans)  //不能漏掉dp[i+MAX]>0,否则会出现虽然总数达到最大,
            ans = dp[i+MAX]+i;                 //但f总和小于0的情况;如题目HINT
    }
    printf("%d\n",ans);
}


这题还可以直接转换成01背包的思想;(你可以对比时间发现时间比上面的慢了一半)

转台转移方程也和01背包的相同:

dp[j] = max(dp[j-S[i]]+F[i],dp[j]);

当然同样要注意 顺序和逆序;

转换01背包代码:

//Problem: 2184		User: fuqiang
//Memory: 1484K		Time: 110MS
//Language: G++		Result: Accepted

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define MAX 100000
#define N 100
#define INF 999999999
int dp[MAX*2+5];  //dp[i+MAX]总的s值为i时的最优的f值(其实就是说两个都是和的形式,因为选了f就选了那个s)
int S[N+5],F[N+5];

int main()
{
    int n,s,f,p = 0;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&s,&f);
        if(!(s<=0&&f<=0))
        {
            S[p] = s;
            F[p++] = f;
        }
    }
    n = p;
    for(int i = 0; i <= MAX*2; i++)
        dp[i] = -INF;
    int _min = MAX;
    dp[MAX] = 0;
    for(int i = 0; i < n; i++)
    {
        if(S[i]<0) _min += S[i];
        if(S[i]>0)
        {
            for(int j = MAX*2; j >= _min; j--)
            {
                if(dp[j-S[i]]!=-INF)
                    dp[j] = max(dp[j-S[i]]+F[i],dp[j]);
            }
        }
        else
        {
            for(int j = _min; j <= MAX*2+S[i]; j++)
            {
                if(dp[j-S[i]]!=-INF)
                    dp[j] = max(dp[j-S[i]]+F[i],dp[j]);
            }
        }
    }
    int ans = 0;
    for(int i = MAX; i <= MAX*2; i++)    //剪掉了i<MAX的情况,即相对值i<0的情况;
    {
        //if(dp[i]!=-INF)
        if(dp[i]>0 && dp[i]+i-MAX > ans)  //不能漏掉dp[i]>0,否则会出现虽然总数达到最大,
            ans = dp[i]+i-MAX;                 //但f总和小于0的情况;如题目HINT
    }
    printf("%d\n",ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值