01背包
朴素版01背包
cin >> n >> m;
f[0][0] = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j <= m; j ++)
{
f[i][j] = f[i - 1][j];//第i个物品不选
if(j - v[i] >= 0)
{
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);//选第i个物品
}
}
}
cout << f[n][m];
一维
cin >> n >> m;
for(int i = 1; i <= n; i ++)cin >> v[i] >> w[i];
//初始化
//一维的要f[0~m]都是0
//如果要恰好背包容量是m的话,要f[0] = 0, f[1~m] = 负无穷
for(int i = 1; i <= n; i ++)
{
for(int j = m; j >= v[i]; j --)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
01背包方案数
f[0] = 1;
for(int i = 1; i <= n; i ++)
{
cin >> v[i];
for(int j = m; j >= v[i]; j --)
{
f[j] += f[j - v[i]];
}
}
cout << f[m];
f[i][j]表示从前i中选,恰好等于j的方案数。
试着分析了一下“从前i中选,恰好等于j” 和“从前i中选,小于等于j”,集合划分居然一样,思来想去如果有哪里不同,那应该是初始化吧,前者初始化f[0][0]=1,f[0][1~m]=0,后者初始化f[0][0~m]=1,试了一下输出f[n][m]-f[n][m-1]过了,应该是这样的没错。
完全背包
朴素版
f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w)+...
f[i][j-v]=max(f[i-1][j-v],f[i-1][j-2v]+v,f[i-1][j-3v]+2w)+...
f[i][j-v]+w正好就是后面那一部分
cin >> n >> m;
for(int i = 1; i <= n; i ++)cin >> v[i] >> w[i];
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][j - v[i]] + w[i]);
}
}
cout << f[n][m];
一维
cin >> n >> m;
for(int i = 1; i <= n; i ++)cin >> v[i] >> w[i];
for(int i = 1; i <= n; i ++)
{
for(int j = v[i]; j <= m; j ++)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
装满背包的方案数
f[0] = 1;
for(int i = 1; i <= n; i ++)
{
for(int j = v[i]; j <= m; j ++)
{
f[j] += f[j - v[i]];
}
}
多重背包
二进制优化
每组有s个v和w相同的物品
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef long double ld;
const int N = 10010;
int n, m;
int v[N], w[N];
int f[N];
int cnt;
int main()
{
IOS
cin >> n >> m;
for(int i = 0; i < n; i ++)
{
int a, b, s;
cin >> a >> b >> s;
for(int k = 1; k <= s; k <<= 1)
{
v[++ cnt] = a * k;
w[cnt] = b * k;
s -= k;
}
if(s)v[++ cnt] = a * s, w[cnt] = b * s;
}
for(int i = 1; i <= n; i ++)
for(int j = m; j >= v[i]; j --)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m];
return 0;
}
单调队列优化(滑动窗口优化)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 20010;
int n, m;
int f[N], g[N], q[N];
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
memcpy(g, f, (m + 1) * sizeof (int));
int v, w, s;
cin >> v >> w >> s;
for(int j = 0; j < v; j ++)
{
int hh = 0, tt = -1;
for(int k = j; k <= m; k += v)
{
if(hh <= tt && q[hh] < k - s * v)hh ++;
while(hh <= tt && g[q[tt]] + (k - q[tt]) / v * w <= g[k])tt --;
q[++ tt] = k;
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
cout << f[m];
return 0;
}
分组背包
每组选一个,每个v和w不同
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
int s;
cin >> s;
for(int j = 1; j <= s; j ++)cin >> v[j] >> w[j];
for(int j = m; j >= 0; j --)
{
for(int k = 1; k <= s; k ++)
{
if(j >= v[k])
{
f[j] = max(f[j], f[j - v[k]] + w[k]);
}
}
}
}
cout << f[m];
二维费用背包
假设每个物品价值为1
for(int i = 1; i <= n; i ++)
{
for(int j = m1; j >= v1[i]; j --)
{
for(int k = m2; k >= v2[i]; k --)
{
f[j][k] = max(f[j][k], f[j - v1[i]][k - v2[i]] + 1);
}
}
}
求容量至少为m1、m2时价值最小值:
f[i][j][k]表示从前i个物品中选,体积1>=j,体积2>=k的最小价值
不选第i个:f[i-1][j][k]
选第i个:f[i-1][j-v1][k-v2] + w
此时j-v1是可以小于0的,取max(0, j-v1) k-v2同理
初始化:f[0][0][0] = 0, f[0][j][k] = 正无穷
cin >> m1 >> m2 >> n;
for(int i = 0; i <= m1; i ++)
{
for(int j = 0; j <= m2; j ++)
{
f[i][j] = 2e9;
}
}
f[0][0] = 0;
for(int i = 1; i <= n; i ++)
{
int v1, v2, w;
cin >> v1 >> v2 >> w;
for(int j = m1; j >= 0; j --)
{
for(int k = m2; k >= 0; k --)
{
f[j][k] = min(f[j][k], f[max(0, j - v1)][max(0, k - v2)] + w);
}
}
}
cout << f[m1][m2];
f[i][j]总结三种情况:
体积最多为j的:一般背包问题
体积恰好为j的: f[0][0] = 0,其它的初始化为正无穷或负无穷
体积至少为j的::f[0][0] = 0,其它的初始化为正无穷或负无穷,与第二个不同的地方是j-v可以为负数
不合法状态初始化为正无穷或负无穷,初始化不合法状态是为了不使用这个值
有依赖的背包问题
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 110;
int n, m;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][N];//根节点为i的子树,背包容量最大为j
//朴素写法是f[u][i][j],根节点为u的子树,从前i个孩子中选,背包容量最大为j
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u)
{
for(int i = h[u]; i != -1; i = ne[i])
dfs(e[i]);
for(int i = v[u]; i <= m; i ++)//先加上根节点的值
f[u][i] = w[u];
for(int i = h[u]; i != -1; i = ne[i])//这里不是正经意义上的从前i个中选,顺序不是1、2、3、4、5这种
{
int son = e[i];
for(int j = m; j >= v[u]; j --)//分组背包倒着循环,用的上一层的
{
for(int k = 0; k <= j - v[u]; k ++)//分给孩子的容量不能占用跟根节点的
{//j - v[u] + 1种方案,选一种
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
}
int main()
{
IOS
cin >> n >> m;
int root;
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i ++)
{
int p;
cin >> v[i] >> w[i] >> p;
if(p == -1)root = i;
else add(p, i);
}
dfs(root);
cout << f[root][m];
return 0;
}
贴一下另一种写法。其实把每棵树选子节点的过程看成一次独立的分组背包,最后再加上树的根节点会更好理解。
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define fixed(s) fixed<<setprecision(12)<<s
//#define int long long
using namespace std;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef long long ll;
typedef pair<ll, ll> PLL;
const int N = 110;
int n, m, root;
int v[N], w[N];
int f[N][N];
vector<int> g[N];
void dfs(int u)
{
for(auto son : g[u])
{
dfs(son);
for(int j = m - v[u]; j >= 0; j --)//从m开始枚举也行,但因为用不到,去掉v[u]这个大小会更快一点
{
for(int k = 0; k <= j; k ++)
{
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
for(int i = m; i >= v[u]; i --)f[u][i] = f[u][i - v[u]] + w[u];
for(int i = 0; i < v[u]; i ++)f[u][i] = 0;
}
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
int p;
cin >> v[i] >> w[i] >> p;
if(p == -1)root = i;
else g[p].push_back(i);
}
dfs(root);
cout << f[root][m];
return 0;
}
背包问题求方案数
01背包
朴素版
从前i个物品中选,背包容量最多为j的最大值方案数
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int v[N], w[N];
int f[N][N], g[N][N];
int main()
{
IOS
cin >> n >> m;
for(int i = 0; i < N; i ++)g[i][0] = g[0][i] = 1;
//从前i个物品中选,背包容量为0 从前0个物品中选,背包容量为i
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
for(int j = 0; j <= m; j ++)
{
f[i][j] = f[i - 1][j];
g[i][j] = g[i - 1][j];
if(j >= v[i])
{
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
if(f[i - 1][j - v[i]] + w[i] > f[i - 1][j])g[i][j] = g[i - 1][j - v[i]];
else if(f[i - 1][j - v[i]] + w[i] == f[i - 1][j])
{
g[i][j] = (g[i - 1][j] + g[i - 1][j - v[i]]) % mod;
}
}
}
}
cout << g[n][m];
return 0;
}
一维
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int v[N], w[N];
int f[N], g[N];
int main()
{
IOS
cin >> n >> m;
for(int i = 0; i < N; i ++)g[i] = 1;
//从前i个物品中选,背包容量为0 从前0个物品中选,背包容量为i
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
for(int j = m; j >= v[i]; j --)
{
if(f[j - v[i]] + w[i] > f[j])g[j] = g[j - v[i]];
else if(f[j - v[i]] + w[i] == f[j])
{
g[j] = (g[j] + g[j - v[i]]) % mod;
}
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << g[m];
return 0;
}
朴素版
从前i个物品中选,背包容量恰好为j的最大值方案数
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int v[N], w[N];
int f[N][N], g[N][N];
int main()
{
IOS
cin >> n >> m;
f[0][0] = 0;
for(int i = 1; i < N; i ++)f[0][i] = -2e9;
g[0][0] = 1;//背包容量恰好为j的写法
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
for(int j = 0; j <= m; j ++)
{
f[i][j] = f[i - 1][j];
g[i][j] = g[i - 1][j];
if(j >= v[i])
{
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
if(f[i - 1][j - v[i]] + w[i] > f[i - 1][j])g[i][j] = g[i - 1][j - v[i]];
else if(f[i - 1][j - v[i]] + w[i] == f[i - 1][j])
{
g[i][j] = (g[i - 1][j] + g[i - 1][j - v[i]]) % mod;
}
}
}
}
int maxn = 0;
for(int i = 0; i <= m; i ++)maxn = max(maxn, f[n][i]);
int ans = 0;
for(int i = 0; i <= m; i ++)
{
if(f[n][i] == maxn)
{
ans = (ans + g[n][i]) % mod;
}
}
cout << ans;
return 0;
}
一维
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int v[N], w[N];
int f[N], g[N];
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i < N; i ++)f[i] = -2e9;
g[0] = 1;//背包容量恰好为j的写法
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
for(int j = m; j >= v[i]; j --)
{
if(f[j - v[i]] + w[i] > f[j])g[j] = g[j - v[i]];
else if(f[j - v[i]] + w[i] == f[j])
{
g[j] = (g[j] + g[j - v[i]]) % mod;
}
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
int maxn = 0;
for(int i = 0; i <= m; i ++)maxn = max(maxn, f[i]);
int ans = 0;
for(int i = 0; i <= m; i ++)
{
if(f[i] == maxn)
{
ans = (ans + g[i]) % mod;
}
}
cout << ans;
return 0;
}
背包问题求具体方案
01背包求字典序最小的方案
正常求方案要倒着推,但不是字典序最小的,从后往前遍历物品即可
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
}
for(int i = n; i >= 1; i --)
{
for(int j = 0; 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]);
}
}
}
int t = m;
for(int i = 1; i <= n; i ++)
{
if(t >= v[i])
{
if(f[i][t] == f[i + 1][t - v[i]] + w[i])
{
cout << i << ' ';
t -= v[i];
}
}
}
return 0;
}
------------------------------------此后为例题-----------------------------------------------
能量石
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i块能量石最初包含 Ei单位的能量,并且每秒将失去 Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N 行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x 是组别编号(从 11 开始),y 是可以获得的最大能量值。
数据范围
1≤T≤10,
1≤N≤100,
1≤Si≤100,
1≤Ei≤1e5,
0≤Li≤1e5
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
i,j两块石头
先取i获得的能量:
先取j获得的能量:
sort一下先后顺序就排好了,之后01背包即可
注意状态表示为“背包容量恰好为j”,转移时f[j-v]可能在j-v-1或者更以前时就等于f[j-v]了,并不是j-v是最优的,j-v越大损失的也越大。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 110, M = 10010;
int n;
int f[M];
struct Node
{
int s, e, l;
bool operator<(const Node &t) const
{
return s * t.l < t.s * l;
}
}a[N];
int main()
{
IOS
int _;
cin >> _;
for(int T = 1; T <= _; T ++)
{
cin >> n;
int m = 0;
for(int i = 1; i <= n; i ++)
{
int s, e, l;
cin >> s >> e >> l;
a[i] = {s, e, l};
m += s;
}
sort(a + 1, a + 1 + n);
memset(f, -0x3f, sizeof f);
f[0] = 0;
for(int i = 1; i <= n; i ++)
{
int s = a[i].s, e = a[i].e, l = a[i].l;
for(int j = m; j >= s; j --)
{
f[j] = max(f[j], f[j - s] + max(0, e - (j - s) * l));
}
}
int res = 0;
for(int i = 1; i <= m; i ++)res = max(res, f[i]);
cout << "Case #" << T << ": " << res << endl;
}
return 0;
}