U279964 取硬币
题目描述
现在有 n1+n2 种面值的硬币,其中前 n1 种为普通币,可以取任意枚,后 n2 种为纪念币,每种最多只能取 1 枚,每种硬币有一个面值,问能用多少种方法拼出 m 的面值?
输入格式
第一行包含三个整数 n1,n2,m ,分别表示普通币种类数,纪念币种类数和目标面值;
第二行 n1 个整数,第 i 种普通币的面值 a[i] 。保证 a[i] 为严格升序;
第三行 n2 个整数,第 i 种纪念币的面试 b[i] 。保证 b[i] 为严格升序。
输出格式
共一行,包含一个整数 x ,表示方法总数对 109+7 取模后的结果。
注意,不要忘记取模。
输入输出样例
输入 #1复制
3 1 5
1 2 3
1
输出 #1复制
9
说明/提示
对于 30% 的数据,保证 1≤n1+n2≤10,1≤m≤100,1≤a[i]≤100,1≤b[i]≤100 。 对于 100% 的数据,保证 1≤n1+n2≤100,1≤m≤100000,1≤a[i]≤100000,1≤b[i]≤100000 。
题解
f[i, j]
1. 集合
(1) 从前 i 种物品中选, 总v 恰好等于 j 的所有选法的集合
集合中选法的count
2. 计算集合
f[i, j]
1. 1 ~ n1 // 完全
f[i, j] += f[i - 1, j]
f[i, j] += f[i, j - vi]
2. n1 + 1 ~ n1 + n2 // 01
f[i, j] += f[i - 1, j - vi]
f[i, j] += f[i - 1, j]
3. 边界(初始化)
f[i, 0] = 1;
4. 时间 O(n^2);
代码
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7, N = 110, M = 1e5 + 10;
int n1, n2, m;
int f[M];
int v[N];
int main()
{
cin >> n1 >> n2 >> m;
for (int i = 1; i <= n1; i ++ ) cin >> v[i];
for (int i = n1 + 1; i <= n1 + n2; i ++ ) cin >> v[i];
for (int i = 0; i <= n1 + n2; i ++ ) f[0] = 1;
for (int i = 1; i <= n1; i ++ )
for (int j = v[i]; j <= m; j ++ )
f[j] = (f[j] + f[j - v[i]]) % MOD;
for (int i = n1 + 1; i <= n1 + n2; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = (f[j] + f[j -v[i]]) % MOD;
cout << f[m];
return 0;
}
U280382 多重背包问题
题目描述
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi ,价值是 wi 。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。
输入格式
第一行两个整数,N,V ,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si ,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
输入输出样例
输入 #1复制
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出 #1复制
10
说明/提示
0<N,V≤100
0<vi,wi,si≤100
题解
多重背包
nn 种物品, 每种物品有多件, 每种物品的体积和价值不变
m 背包体积
f[i, j]
1. 集合
f[i, j]表示: 从前 i 种物品中选(每种物品有多件), 所选物品总体积不超过
j 的所有选法的集合
存的是集合中的某一方案, 这个方案价值max
2. 计算集合
f[i, j]
枚举第 i 种物品选 了 几个
(0i, 1i, 2i, 3i, ... ki)
f[i, j] = max(f[i - 1, j - k*vi] + k*wi, f[i, j])
f[i,j]=max(f[i-1,j] ,f[i-1,j-vi]+wi,f[i-1,j-2vi]+2wi...f[i-1,si*vi] +si *wi]);
f[i,j-vi]=max( f[i-1,j-vi] ,f[i-1,j-2vi]+ wi...f[i-1,j-(si-1)vi-vi)+(si-1)wi ,f[i-1,j-(si+1)vi]+si*wi)
3. 边界(初始化)
f[0, j] = 0;
f[i,j]
4. 时间 O(n*m*si)
代码
#include <iostream>
using namespace std;
const int N = 8 * 100, M = 1e4;
int n, m;
int f[N][M];
int v[N], w[N];
int main()
{
cin >> n >> m;
int cnt = 0;
for (int i = 1; i <= n; i ++ )
{
int a, b, s;
cin >> a >> b >> s;
int k = 0; // 2^k
while (s > k)
{
if (k == 0) k ++ ;
cnt ++ ;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
if (s)
{
cnt ++ ;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
n = cnt;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
U280369 分组背包问题
题目描述
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 vij ,价值是 wij ,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V ,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物数量;
每组数据接下来有 Si 行,每行有两个整数 vij, wij,用空格隔开,分别表示第 i 个物品组的第 j个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
输入输出样例
输入 #1复制
3 5
2
1 2
2 4
1
3 4
1
4 5
输出 #1复制
8
说明/提示
0<N,V≤100
0<Si≤100
0<vij,wij≤100
题解
分组背包
n , m
n 物品组
m 背包体积
每组物品里面, 有多种物品,(每种物品只有一件)
给出每组物品里, 每件物品的价值, 体积
求 从 n 组物品中选, 每组物品最 多选一件物品, 也可以不选
总体积不超过 m , 总价值最大
f[i, j]
f[i, j] = k;
1. 集合
表示: n 组物品中选, 每组物品最 多选一件物品, 也可以不选
总体积不超过 m 的所有选法的集合
(属性, 数)存的数是这个集合中的某一个方案, 这个方案总价值最大
max(价值)
2. 计算集合
f[i, j] ---> 包含的子集, 只要找 到所有子集取 Max
在这个题里面, 每个子集就是一种方案
max(1, 2, 3, 4, ...i);
f[i, j]
不选 第 i 组的物品
f[i, j] = max(f[i, j], f[i - 1, j])
选 第 i 组的物品
选第 i 组的哪一个物品
k: 第 i 组 物品中第 k 个物品
f[i, j] = max(f[i - 1, j - vik] + wik, f[i, j])
f[i,j] = max(f[i-1,j], f[i-1,j-vi1]+wi1, f[i-1,j-vi2] +wi2...f[i-1,j-vik] + wik)
f[i,j-vi1] = max(f[i-1,j-vi1],f[i-1,j-2vi1]+wi1, f[i-1,j-vi1-vi2]+wi2...f[i-1,j-vi1-vik]+wik)
3. 边界: f[0, j] = 0;
4. O(n^3)
代码
#include <iostream>
using namespace std;
const int N = 110;
int n, m;
int f[N][N];
int s[N], v[N][N], w[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 1; j <= s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ ) // 物品组
for (int j = 1; j <= m; j ++ ) // 体积
{
f[i][j] = f[i - 1][j];
for (int k = 1; k <= s[i]; k ++ ) // 每组物品中的每个物品
if (v[i][k] <= j)
f[i][j] = max(f[i - 1][j - v[i][k]] + w[i][k], f[i][j]);
}
cout << f[n][m] << endl;
return 0;
}
U280641 宠物小精灵之收服
题目描述
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。
输入输出样例
输入 #1复制
10 100 5
7 10
2 40
2 50
1 20
4 20
输出 #1复制
3 30
输入 #2复制
10 100 5
8 110
12 10
20 10
5 200
1 110
输出 #2复制
0 100
说明/提示
0<N≤1000 , 0<M≤500 , 0<K≤100
题解
f[i, j1, j2]
1. 集合 : 从 前 i 件物品中选, 一维体积不超过 j1 (精灵球)
二维体积不超过 j2 (体力),的所有选法的集合
w[i] = 1, 小精灵的价值看成 1
max(价值)
2. 计算集合
f[i, j1, j2] v1[i], v2[i]
第 i 件物品 选 或 不选
f[i, j1, j2] = max(f[i - 1, j1, j2], f[i - 1, j1 - v1i, j2 - v2i] + 1);
3. 边界
f[0, j1, j2] = 0
4. 时间: O(n^3)
代码
#include <iostream>
using namespace std;
const int N = 1010, M = 510, K = 110;
int n, m, k;
int f[N][M];
int v1[K], v2[K];
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= k; i ++ ) cin >> v1[i] >> v2[i];
for (int i = 1; i <= k; i ++) // 物品数
for (int j1 = n; j1 >= v1[i]; j1 -- ) // 一维体积(精灵球)
for (int j2 = m; j2 >= v2[i]; j2 -- )
f[j1][j2] = max(f[j1][j2], f[j1 - v1[i]][j2 - v2[i]] + 1);
int res = -1, t; // res 代表max(价值), t 消耗的二维体积(体力)
for (int i = 0; i <= m - 1; i ++ )
if (res < f[n][i])
{
res = f[n][i];
t = i;
}
cout << res << ' ' << m - t << endl;
return 0;
}