今天做题的时候发现了一个问题,废话不多说,先上题目链接: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[i−1]+dp[i−5]+dp[i−10]+dp[i−25]+dp[i−50]
我们可以假设现在找给别人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)是同一种情况,不能重复计算,所以就不能用第二种这样计算。
现在我们再回到第一题,因为优惠每次都可以选,所以我们可以当作完全背包,但是我们不需要保证他选择的优惠是顺序的,只要优惠就行,不在乎优惠的顺序,所以就用适用于第二种的情况。