完全背包的for循环互换问题

22 篇文章 0 订阅

今天做题的时候发现了一个问题,废话不多说,先上题目链接:F. Shovels Shop
先说一下题意:
商店有卖n个铁锹,你要取买k把回去(多一把,少一把都不行),然后有m种优惠(x,y),优惠的意思就是,你买x把,给你免费其中最便宜的y把。但是每次买只能使用一次优惠,不可叠加,但是我的这k把可以分好几个批次买。
思路:完全背包。
wa代码:

/*
 * @沉着,冷静!: 噗,这你都信!
 * @LastEditors: HANGNAG
 * @LastEditTime: 2020-08-19 17:51:44
 * @FilePath: \ACM_vscode\codeforces\cf.cpp
 */
#include <algorithm>
#include <iostream>
#include <map>
#include <math.h>
#include <queue>
#include <set>
#include <stack>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#define emplace_back push_back
#define pb push_back
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
const double eps = 1e-6;
const LL inf = 0x3f3f3f3f;
const LL N = 2e5 + 10;
LL dp[N];
LL a[N];
LL sum[N];
struct zxc
{
    LL w, v;
} s[N];
bool cmp(zxc q,zxc e)
{
    if(q.w==e.w)
    {
        return q.v > e.v;
    }
    return q.w < e.w;
}
int main()
{
    LL n, m, k;
    scanf("%lld%lld%lld", &n, &m, &k);
    for (LL i = 1; i <= n;i++)
    {
        scanf("%lld", &a[i]);
    }
    for (LL i = 1; i <= m;i++)
    {
        scanf("%lld%lld", &s[i].w, &s[i].v);
    }
    sort(a + 1, a + 1 + n);
    sort(s + 1, s + 1 + m, cmp);
    for (int i = 1; i <= n;i++)
    {
        sum[i] = sum[i - 1] + a[i];
    }
        for (LL i = 1; i <= k; i++)
        {
            dp[i] = sum[i];
        }
    for (LL i = 1; i <= m;i++)
    {
        for (LL j = s[i].w; j <=k;j++)
        {
            dp[j] = min(dp[j], dp[j - s[i].w] + (sum[j] - sum[j - s[i].w + s[i].v]));
        }
    }
    printf("%lld\n", dp[k]);
    return 0;
}

AC代码:

/*
 * @沉着,冷静!: 噗,这你都信!
 * @LastEditors: HANGNAG
 * @LastEditTime: 2020-08-19 18:25:52
 * @FilePath: \ACM_vscode\codeforces\cf.cpp
 */
#include <algorithm>
#include <iostream>
#include <map>
#include <math.h>
#include <queue>
#include <set>
#include <stack>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#define emplace_back push_back
#define pb push_back
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
const double eps = 1e-6;
const LL inf = 0x3f3f3f3f;
const LL N = 2e5 + 10;
LL dp[N];
LL a[N];
LL sum[N];
struct zxc
{
    LL w, v;
} s[N];
bool cmp(zxc q,zxc e)
{
    if(q.w==e.w)
    {
        return q.v>e.v;
    }
    return q.w < e.w;
}
int main()
{
    LL n, m, k;
    scanf("%lld%lld%lld", &n, &m, &k);
    for (LL i = 1; i <= n;i++)
    {
        scanf("%lld", &a[i]);
    }
    for (LL i = 1; i <= m;i++)
    {
        scanf("%lld%lld", &s[i].w, &s[i].v);
    }
    sort(a + 1, a + 1 + n);
    sort(s + 1, s + 1 + m, cmp);
    for (int i = 1; i <= n;i++)
    {
        sum[i] = sum[i - 1] + a[i];
        dp[i] = sum[i];
    }
    for (LL j = 1; j <=k;j++)
    {
        for (LL i = 1; i <= m;i++)
        {
            if(s[i].w<=j)
            dp[j] = min(dp[j], dp[j - s[i].w] + (sum[j] - sum[j - s[i].w + s[i].v]));
        }
    }
    
        printf("%lld\n", dp[k]);
    return 0;
}

可以看出区别就是完全背包遍历的时候的两个for循环的顺序。其实我也不知道为啥,就又去做了一道比较简单的完全背包的题来验证一下不同顺序的for循环的区别:
https://vjudge.net/problem/UVA-674
题意就是给你要找给别人钱n,问你用1,5,10,25,50的零钱,能又多少种不同的找钱方式。
这个题的状态转移很好找就是
d p [ i ] = d p [ i − 1 ] + d p [ i − 5 ] + d p [ i − 10 ] + d p [ i − 25 ] + d p [ i − 50 ] dp[i]=dp[i-1]+dp[i-5]+dp[i-10]+dp[i-25]+dp[i-50] dp[i]=dp[i1]+dp[i5]+dp[i10]+dp[i25]+dp[i50]
我们可以假设现在找给别人6元钱,但是我们发现(1,5)和(5,1)应该是一种。
第一种

dp[0] = 1;
        for (int i = 0; i <= 4; i++)
        {
            for (int j =v[i]; j <=7490; j++)
            {
                
                    dp[j] += dp[j - v[i]];
            }
            }

这种每次把这种零钱能找就都找了,不会出现乱序,换句话说就是,假设现在找给我的零钱有顺序
假设现在要找给我22元,这种有这几种方式
(1,1,1,。。。。。。1,1,)
(1,1,1,1,1.。。。,1,5,5)
(1,1,5,5,10)
(1,1,10,10)
等等。。。
但是他不会出现(1,10,1,10)或者(1,5,1,5,10)这种打乱顺序的情况出现。
他最后一定是(一段1,一段5,一段10,一段25,一段50),我说的一段也可能的0。
但是这种就不一样了

dp[0] = 1;
for (int j = v[i]; j <= 7490; j++)
{
    for (int i = 0; i <= 4; i++)
    {
        if (j >= v[i])
            dp[j] += dp[j - v[i]];
    }
}

这一种就会出现上面的慌乱的顺序。
但是如果是对这个顺序没有要求和影响的,其实这两种都没有区别,但是比如第二题,(1,5)和(5,1)是同一种情况,不能重复计算,所以就不能用第二种这样计算。
现在我们再回到第一题,因为优惠每次都可以选,所以我们可以当作完全背包,但是我们不需要保证他选择的优惠是顺序的,只要优惠就行,不在乎优惠的顺序,所以就用适用于第二种的情况。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值