01背包问题:
状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合
集 合 划 分 : 对 于 每 个 物 体 只 能 选 和 不 选 集合划分:对于每个物体只能选和不选 集合划分:对于每个物体只能选和不选
状 态 转 移 方 程 i f ( j > v i ) f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] ) e l s e f [ i ] [ j ] = f [ i − 1 ] [ j ] 状态转移方程if(j>v_i)f[i][j]=max(f[i-1][j],f[i-1][j-v_i])elsef[i][j]=f[i-1][j] 状态转移方程if(j>vi)f[i][j]=max(f[i−1][j],f[i−1][j−vi])elsef[i][j]=f[i−1][j]
很 明 显 f [ i ] [ j ] 只 用 上 一 层 的 数 据 , 所 以 我 们 可 以 压 掉 一 维 并 且 逆 序 循 环 保 证 很明显f[i][j]只用上一层的数据,所以我们可以压掉一维并且逆序循环保证 很明显f[i][j]只用上一层的数据,所以我们可以压掉一维并且逆序循环保证 用 上 一 层 f [ i − 1 ] 的 数 据 更 新 的 用上一层f[i-1]的数据更新的 用上一层f[i−1]的数据更新的
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
typedef pair<int,int> PII;
PII ch[N];
int n, maxv;
int res[N];
int main()
{
cin >> n >> maxv;
for(int i = 1; i <= n; ++ i)
cin >> ch[i].first >> ch[i].second;
for(int i = 1; i <= n; ++ i)
for(int j = maxv; j >= ch[i].first; -- j)
res[j] = max(res[j],res[j - ch[i].first] + ch[i].second);
cout << res[maxv] << endl;
return 0;
}
完全背包问题:
状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合
集 合 划 分 : 对 于 每 个 物 体 有 选 1..... + ∞ [ 体 积 上 限 ] 集合划分:对于每个物体有选1.....+\infty[体积上限] 集合划分:对于每个物体有选1.....+∞[体积上限]
状 态 转 移 方 程 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v i ] ) + k ∗ w i k ∈ [ 0 , s ] 状态转移方程f[i][j]=max(f[i-1][j-k*v_i])+k*w_ik\in[0,s] 状态转移方程f[i][j]=max(f[i−1][j−k∗vi])+k∗wik∈[0,s]
优
化
:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
k
∗
v
i
]
)
+
k
∗
w
i
∣
k
∈
[
0
,
s
]
优化:f[i][j]=max(f[i-1][j-k*v_i])+k*w_i|k\in[0,s]
优化:f[i][j]=max(f[i−1][j−k∗vi])+k∗wi∣k∈[0,s]
f
[
i
]
[
j
−
v
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
k
∗
v
i
]
)
+
(
k
−
1
)
∗
w
i
∣
k
∈
[
1
,
s
]
f[i][j-v]=max(f[i-1][j-k*v_i])+(k-1)*w_i|k\in[1,s]
f[i][j−v]=max(f[i−1][j−k∗vi])+(k−1)∗wi∣k∈[1,s]
对
比
两
个
表
达
式
:
对比两个表达式:
对比两个表达式:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
v
]
+
w
)
f[i][j]=max(f[i-1][j],f[i][j-v]+w)
f[i][j]=max(f[i−1][j],f[i][j−v]+w)
很
明
显
更
新
当
前
数
据
只
要
前
层
f
[
i
]
的
j
体
积
比
较
小
的
所
以
应
该
从
小
到
大
枚
举
体
积
。
很明显更新当前数据只要前层f[i]的j体积比较小的所以应该从小到大枚举体积。
很明显更新当前数据只要前层f[i]的j体积比较小的所以应该从小到大枚举体积。
第
一
维
物
体
,
第
二
维
循
环
体
积
,
第
三
维
循
环
决
策
第一维物体,第二维循环体积,第三维循环决策
第一维物体,第二维循环体积,第三维循环决策
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e4 + 10;
PII c[N];
int n, maxv;
int res[N];
int main()
{
cin >> n >> maxv;
for(int i = 1; i <= n; ++ i)
cin >> c[i].first >> c[i].second;
for(int i = 1; i <= n; ++ i)
for(int j =c[i].first; j <= maxv; ++ j)
res[j] = max(res[j],res[j - c[i].first] + c[i].second);
cout << res[maxv] << endl;
}
多重背包[二进制优化版本]
状 态 表 示 : f [ i ] [ j ] 表 示 从 只 从 前 i 个 物 体 里 面 选 , 切 总 体 积 不 超 过 j 的 选 法 的 集 合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合 状态表示:f[i][j]表示从只从前i个物体里面选,切总体积不超过j的选法的集合
集 合 划 分 : 对 于 每 个 物 体 有 选 1..... + ∞ [ a [ i ] [ 物 体 个 数 有 限 ] ] 集合划分:对于每个物体有选1.....+\infty[a[i][物体个数有限]] 集合划分:对于每个物体有选1.....+∞[a[i][物体个数有限]]
我 们 可 以 将 a [ i ] 进 行 二 进 制 拆 分 , 因 为 一 个 数 可 以 分 成 若 干 个 数 之 和 。 这 样 就 变 成 01 背 包 了 我们可以将a[i]进行二进制拆分,因为一个数可以分成若干个数之和。这样就变成01背包了 我们可以将a[i]进行二进制拆分,因为一个数可以分成若干个数之和。这样就变成01背包了
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 200;
int res[N];
int v[N], w[N], num[N];
int n, maxv;
int main()
{
cin >> n >> maxv;
for(int i = 1; i <= n; ++ i)
cin >> v[i] >> w[i] >> num[i];
vector<PII> goods;
for(int i = 1; i <= n; ++ i)
{
for(int k = 1; k <= num[i]; k *= 2)
{
num[i] -= k;
goods.push_back({v[i] * k, w[i] * k});
}
if(num[i] > 0)
goods.push_back({v[i] * num[i],w[i] * num[i]});
}
for(auto it : goods)
{
for(int j = maxv; j >= it.first; j --)
res[j] = max(res[j],res[j - it.first] + it.second);
}
cout << res[maxv] << endl;
return 0;
}
多重背包[单调队列优化]
注释:w[i]是物体体积,v[i]是价格,num[i]是物体个数
我们就看一下原始的公式
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
−
k
∗
w
[
i
]
]
+
k
∗
v
[
i
]
)
k
∈
[
0
,
m
i
n
(
j
/
w
[
i
]
,
n
u
m
[
i
]
)
]
dp[i][j]=max(dp[i-1][j-k*w[i]]+k*v[i])k\in[0,min(j/w[i],num[i])]
dp[i][j]=max(dp[i−1][j−k∗w[i]]+k∗v[i])k∈[0,min(j/w[i],num[i])](体积有限要取min)
我们进行数学转化
我
们
设
j
=
a
∗
w
[
i
]
+
m
o
d
我们设j=a*w[i]+mod
我们设j=a∗w[i]+mod
d
p
[
i
]
[
a
∗
w
[
i
]
+
m
o
d
]
=
m
a
x
(
d
p
[
i
−
1
]
[
a
∗
w
[
i
]
+
m
o
d
−
k
∗
w
[
i
]
]
+
k
∗
v
[
i
]
)
;
dp[i][a*w[i]+mod]=max(dp[i-1][a*w[i]+mod-k*w[i]]+k*v[i]);
dp[i][a∗w[i]+mod]=max(dp[i−1][a∗w[i]+mod−k∗w[i]]+k∗v[i]);
合
并
同
类
项
合并同类项
合并同类项
d
p
[
i
]
[
a
∗
w
[
i
]
+
m
o
d
]
=
m
a
x
(
d
p
[
i
−
1
]
[
(
a
−
k
)
∗
w
[
i
]
+
m
o
d
]
+
k
∗
v
[
i
]
)
;
dp[ i ][ a*w[i]+mod] = max( dp[ i-1 ][ (a-k)*w[i]+mod] + k*v[i] );
dp[i][a∗w[i]+mod]=max(dp[i−1][(a−k)∗w[i]+mod]+k∗v[i]);
最关键的一步:令
t
=
a
−
k
则
k
=
a
−
t
t=a-k则k=a-t
t=a−k则k=a−t
化
简
得
:
d
p
[
i
]
[
a
∗
w
[
i
]
+
m
o
d
]
=
m
a
x
(
d
p
[
i
−
1
]
[
t
∗
w
[
i
]
+
m
o
d
]
)
+
(
a
−
t
)
∗
v
[
i
]
;
化简得:dp[ i ][ a*w[i]+mod] = max( dp[ i-1 ][ t*w[i]+mod] ) + (a-t)*v[i];
化简得:dp[i][a∗w[i]+mod]=max(dp[i−1][t∗w[i]+mod])+(a−t)∗v[i];
移
项
得
:
d
p
[
i
]
[
a
∗
w
[
i
]
+
m
o
d
]
−
a
∗
v
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
[
t
∗
w
[
i
]
+
b
]
−
t
∗
v
[
i
]
)
移项得:dp[i][ a*w[i]+mod] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i])
移项得:dp[i][a∗w[i]+mod]−a∗v[i]=max(dp[i−1][t∗w[i]+b]−t∗v[i])
这
时
候
发
现
这
等
式
两
端
是
同
型
的
这时候发现这等式两端是同型的
这时候发现这等式两端是同型的
1.
注
意
这
里
的
a
和
n
u
m
[
i
]
是
没
有
关
系
的
,
因
为
a
是
j
拆
成
a
∗
w
[
i
]
+
m
o
d
形
式
上
的
一
个
系
数
1.注意这里的a和num[i]是没有关系的,因为a是j拆成a*w[i]+mod形式上的一个系数
1.注意这里的a和num[i]是没有关系的,因为a是j拆成a∗w[i]+mod形式上的一个系数
2.
我
们
再
看
看
t
,
t
=
a
−
k
,
因
为
k
是
变
化
的
。
那
么
t
也
是
变
化
的
2.我们再看看t,t=a-k,因为k是变化的。那么t也是变化的
2.我们再看看t,t=a−k,因为k是变化的。那么t也是变化的
对于一个 d p [ i ] [ j ] 我 们 可 以 通 过 枚 举 m o d 和 a 去 取 遍 ∈ [ 0 , j ] dp[i][j]我们可以通过枚举mod和a去取遍\in[0,j] dp[i][j]我们可以通过枚举mod和a去取遍∈[0,j]中的所有值当 a a a确定了之后对于 t 就 是 从 ∈ [ a − m i n ( j / w [ i ] , n u m [ i ] ) , a ] t就是从\in[a-min(j/w[i],num[i]),a] t就是从∈[a−min(j/w[i],num[i]),a]很明显 t t t这个"|变化的区间长度是固定的等于k|"这样就类似滑动窗口在固定长度里面求最大值,用单调队列维护就可以 O ( 1 ) O(1) O(1)转移了
#include <iostream>
#include <cstdio>
#include <stack>
#include <sstream>
#include <vector>
#include <map>
#include <cstring>
#include <deque>
#include <cmath>
#include <iomanip>
#include <queue>
#include <algorithm>
#include <set>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define hash Hash
#define next Next
#define count Count
#define pb push_back
#define f first
#define s second
using namespace std;
const int N = 1e5+10;
const double eps = 1e-10;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
template<typename T> void read(T &x)
{
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args)
{
read(first);
read(args...);
}
int n, m;
int w[N], v[N], num[N];
int dp[1100][20010];
int q[N];
int main()
{
read(n,m);
_rep(i,1,n)
read(w[i],v[i],num[i]);
//枚举物体个数;
_rep(i,1,n)
{
_rep(mod,0,w[i]-1)//本来是j,我们变成枚举mod和k;
{
int hd = 1, tl = 0;
for(int k = 0; k*w[i] + mod <= m; ++ k)
{
int now = dp[i - 1][k * w[i] + mod] - k*v[i];
while(k - q[hd] > min(num[i],(k*w[i]+mod)/w[i]) && hd <= tl) hd ++;//单调队列里面储存的是idx不是dp值,如果idx和当前的差值超了就pop掉
while(dp[i - 1][q[tl]*w[i] + mod] - q[tl]*v[i]<=now && hd <= tl) tl--;
q[++ tl] = k;
dp[i][k*w[i] + mod] = dp[i - 1][q[hd]*w[i] + mod] - q[hd]*v[i] + k*v[i];
}
}
}
cout << dp[n][m] << endl;
return 0;
}