文章目录
装箱问题
装箱问题
题目描述
有一个箱子容量为
V
V
V(正整数,
0
≤
V
≤
20000
0 ≤ V ≤ 20000
0≤V≤20000),同时有
n
n
n 个物品
(
0
<
n
≤
30
)
(0<n ≤ 30)
(0<n≤30),每个物品有一个体积(正整数)。
要求
n
n
n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入描述
1
1
1个整数,表示箱子容量
1
1
1个整数,表示有
n
n
n 个物品
接下来
n
n
n 行,分别表示这
n
n
n 个物品的各自体积
输出描述
1
1
1个整数,表示箱子剩余空间。
输入样例
24 6
8 3 12 7 9 7
输出样例
0
1.确定状态:开个bool 数组表示
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示前
i
i
i 个物品能否组成体积
j
j
j
2.确定状态转移方程:枚举最后一次的决策,第
i
i
i 个物品是放还是不放
if(dp[i-1][j])
{
dp[i][k+v[i]]=1; //放第 i 个物品
dp[i][k]=1; //不放第 i 个物品
}
初值dp[i][j]=0;dp[0][0]=1;
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | T | ||||||||||||||||||||||||
1 | T | T | |||||||||||||||||||||||
2 | T | T | T | T | |||||||||||||||||||||
3 | T | T | T | T | T | T | T | T | |||||||||||||||||
4 | T | T | T | T | T | T | T | T | T | T | T | ||||||||||||||
5 | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | ||||||||
6 | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | T | |||||||
**01滚动 – dp[i][0,1] 一行记录前一行的值,另一行记录当前行的值。 | |||||||||||||||||||||||||
就地滚动:用一个一维数组,之前的状态和当前状态记在同一个数组里。** | |||||||||||||||||||||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | |
– | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | |
T | T | T | T | T | T | T | T | T | T | T |
#include <iostream>
using namespace std;
int v,n,value[40],dp[20010];
int main()
{
cin>>v>>n;
for(int i=1;i<=n;++i) cin>>value[i];
for(int i=1;i<=n;++i)
for(int j=v;j>=value[i];j--)
if(dp[j-value[i]])
dp[j]=dp[j-value[i]];
cout<<dp[v]<<'\n';
return 0;
}
背包
01背包
01背包是在
M
M
M 件物品中取出若干件放在空间为
W
W
W 的背包,每件物品的体积为
W
1
W_1
W1,
W
2
W_2
W2,…
W
n
W_n
Wn,对应的价值是
P
1
P_1
P1,
P
2
P_2
P2 …
P
n
P_n
Pn。
01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在 01 背包问题中,每个物品有且只有一个,对于每个问题只考虑选或者不选两种情况。如果选择放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。
背包问题
背包问题
题目大意
01
01
01 背包是是一个普通的动态规划入门问题:
一共有
n
n
n 个物品, 第
i
i
i 个物品的体积为
v
[
i
]
v[i]
v[i];
有一个背包容量为
m
m
m,现在我要挑选一些物品放入这个背包
我现在知道在总体积不超过背包容量的情况下,他一共有多少种放法(总体积为0也算一种放法)。
1
<
=
n
<
=
30
,
1
<
=
m
,
v
[
i
]
<
=
1
e
9
1 <= n <= 30, 1 <= m , v[i]<= 1e^9
1<=n<=30,1<=m,v[i]<=1e9
这就是一个很简单的01背包问题,我可以告诉你核心代码怎么写:
很简单吧,但是……,你试一试吧。
输入描述
输入有多组,每一组第一行是
n
n
n 和
m
m
m
接下来第二行到第
n
+
1
n+1
n+1 行,第
i
+
1
i+1
i+1 行表示
v
[
i
]
v[i]
v[i]。
输出描述
输出每个样例的方案数,每个答案占据一行。
输入样例
3 10
1
2
4
输出样例
8
#include <cstdio>
using namespace std;
int n,m,a[55],num;
void dfs(int x,int sum)
{
if(x == n+1)
{
num++;
return ;
}
dfs(x + 1, sum);
if(sum + a[x] <= m) dfs(x + 1, sum + a[x]);
}
int main()
{
while(~scanf(" %d%d",&n,&m))
{
num = 0;
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
dfs(1,0);
printf("%d\n",num);
}
return 0;
}
01背包输出方案
UVA 624 CD
题目大意
数据有若干组,第一个数
v
v
v 表示拥有的权值,第二个数
n
n
n 件物品,紧接着
n
n
n 个数,每个数的权值和代价都是
n
i
n_i
ni,在花费不超过
v
v
v 的前提下能够拥有的最大权值总和是多少,每组数据输出 “每组的方案
s
u
m
sum
sum: 最大权值总和”
#include <cstdio>
#include <cstring>
using namespace std;
const int maxv=1e5+7;
const int maxn=30;
int dp[maxv];
int w[maxn],t[maxn];
int g[maxn][maxv];
int ans[maxn];
int main()
{
int v,n;
while(~scanf(" %d %d",&v,&n))
{
memset(dp,0,sizeof(dp));
memset(g,0,sizeof(g));
int cnt=0;
for(int i=1;i<=n;++i) scanf(" %d",&w[i]);
for(int i=1;i<=n;++i)
for(int j=v;j>=w[i];--j)
if(dp[j-w[i]]+w[i]>dp[j])
{
dp[j]=dp[j-w[i]]+w[i];
g[i][j]=1;
}
for(int i=n,j=v;i>=1&&j>=0;--i)
{
if(g[i][j])
{
ans[++cnt]=w[i];
j-=w[i];
}
}
for(int i=cnt;i;--i) printf("%d ",ans[i]);
printf("sum:%d\n",dp[v]);
}
return 0;
}
01背包问题
题目大意
有
N
N
N 件物品和一个容量是
V
V
V 的背包。每件物品只能使用一次。
第
i
i
i 件物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
输入格式
第一行两个整数
N
N
N,
V
V
V,用空格隔开,分别表示物品数量和背包容积。
接下来有
N
N
N 行,每行两个整数
v
i
,
w
i
v_i,w_i
vi,wi,用空格隔开,分别表示第
i
i
i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
1000
,
0
<
v
i
,
w
i
≤
1000
0<N,V≤1000,0<v_i,w_i≤1000
0<N,V≤1000,0<vi,wi≤1000
输入样例
6 12
5 10
3 7
2 4
4 3
5 17
4 8
输出样例
8
确定状态:
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 个物品,背包容量
j
j
j 的背包可获得的最大价值。
确定状态转移方程:
1.不放当前物品
f
[
i
]
[
j
]
f[i][j]
f[i][j] =
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]。
2.放当前物品
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
c
[
i
]
]
+
w
[
i
]
f[i][j] = f[i-1][j-c[i]] + w[i]
f[i][j]=f[i−1][j−c[i]]+w[i]
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
c
[
i
]
]
+
w
[
i
]
)
f[i][j] = max(f[i-1][j],f[i-1][j-c[i]] + w[i])
f[i][j]=max(f[i−1][j],f[i−1][j−c[i]]+w[i])
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | -inf | -inf | -inf | -inf | -inf | -inf | -inf | -inf | -inf | -inf | -inf | -inf |
1 | 0 | 10 | |||||||||||
2 | 0 | 7 | 10 | 17 | |||||||||
3 | 0 | 4 | 7 | 11 | 14 | 17 | 21 | ||||||
4 | 0 | 4 | 7 | 3 | 11 | 10 | 14 | 17 | 21 | 21 | |||
5 | 0 | 4 | 7 | 3 | 17 | 10 | 21 | 24 | 20 | 28 | 27 | 31 | |
6 | 0 | 4 | 7 | 8 | 17 | 12 | 21 | 24 | 25 | 28 | 29 | 32 | |
就地滚动: |
for(int i=1;i<=n;i++)
for(int j=m;j>=c[i];j--)
if(f[j-c[i]]+w[i]>f[j])
f[j]=f[j-c[i]]+w[i];
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
memset(f,0,sizeof f);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) scanf("%d%d",&v[i],&w[i]);
f[0] = 0;
for(int i = 1; i <= n; i++)
for(int j = m; j >= v[i]; j--)
if(f[j-v[i]]+w[i]>f[j])
f[j]=f[j-v[i]]+w[i];
printf("%d\n",f[m]);
return 0;
}
Bone Collector
Bone Collector
题目大意
有一个体积为
v
v
v 的背包,有不同价值和不同体积的物品,在不超过背包的体积下能获得的最大价值是多少?
输入格式
输入一个
t
t
t,表示有
t
t
t 组数据。
每组数据两个数
n
n
n 和
v
v
v,表示物品的总数和背包的体积。
接下一行输入
n
n
n 个数,表示有
n
n
n 个物品的价值。
最后一行输入
n
n
n 个数,表示有
n
n
n 个物品的体积。
输出格式
每组数据输出一行,表示最大的价值。
输入样例
1
5 10
1 2 3 4 5
5 4 3 2 1
输出样例
14
确定状态:
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示容量为
j
j
j 的背包放完第
i
i
i 个物品得到的最大价值
确定状态转移方程:
1.不放当前物品
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j] = f[i-1][j]
f[i][j]=f[i−1][j]
2.放当前物品
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
f[i][j] = f[i-1][j-v[i]]+w[i]
f[i][j]=f[i−1][j−v[i]]+w[i]
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i])
f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+w[i])
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+7;
int dp[maxn];
int N,V,t;
int v[maxn],c[maxn];
int main()
{
scanf(" %d",&t);
while(t--)
{
memset(dp,0,sizeof(dp));
scanf(" %d%d",&N,&V);
for(int i=1;i<=N;++i) scanf(" %d",&v[i]);
for(int i=1;i<=N;++i) scanf(" %d",&c[i]);
for(int i=1;i<=N;i++)
for(int j=V;j>=c[i];--j)
if(dp[j-c[i]]+v[i]>dp[j]) dp[j]=dp[j-c[i]]+v[i];
printf("%d\n",dp[V]);
}
return 0;
}
完全背包
有
n
n
n 种物品和一个容量为
v
v
v 的背包,第
i
i
i 种物品有若干件可用,每件费用是
c
[
i
]
c[i]
c[i],价值是
w
[
i
]
w[i]
w[i]。求在不超过背包体积的情况下获得的最大权值。
01 背包和完全背包的区别在于 01 背包每件物品只能使用一次,完全背包中的物品可以无限使用。
完全背包问题
题目大意
有
N
N
N 种物品和一个容量是
V
V
V 的背包,每种物品都有无限件可用。
第
i
i
i 种物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
输入格式
第一行两个整数
N
,
V
N,V
N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有
N
N
N 行,每行两个整数
v
i
,
w
i
v_i,w_i
vi,wi,用空格隔开,分别表示第
i
i
i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
1000
0<N,V≤1000
0<N,V≤1000
0
<
v
i
,
w
i
≤
1000
0<v_i,w_i≤1000
0<vi,wi≤1000
输入样例
1 25
3 5
输出样例
40
1.确定状态
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 种物品恰放入容量为
j
j
j 的背包的最大权值。
状态转移
1.不放当前物品
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j] = f[i-1][j]
f[i][j]=f[i−1][j]
2.放当前物品
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
k
∗
v
[
i
]
]
+
k
∗
w
[
i
]
f[i][j] = f[i-1][j-k*v[i]]+k*w[i]
f[i][j]=f[i−1][j−k∗v[i]]+k∗w[i]
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
k
∗
v
[
i
]
]
+
k
∗
w
[
i
]
)
(
0
≤
k
∗
v
[
i
]
≤
j
)
f[i][j] = max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]) (0 \leq k*v[i] \leq j)
f[i][j]=max(f[i−1][j],f[i−1][j−k∗v[i]]+k∗w[i])(0≤k∗v[i]≤j)
**时间复杂度:
O
(
V
∗
∑
(
V
v
[
i
]
)
∗
∗
O(V*\sum(\frac{V}{v[i]})**
O(V∗∑(v[i]V)∗∗
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 5 | 5 | 5 | 10 | 10 | 10 | 15 | 15 | 15 | 20 | 20 | 20 | 25 | 25 | 25 | 30 | 30 | 30 | 35 | 35 | 35 | 40 | 40 |
for(int i=1;i<=n;i++)
for(int j=c[i];j<=V;j++)
if(f[j-c[i]]+w[i]>f[j])
f[j]=f[j-c[i]]+w[i];
#include <stdio.h>
using namespace std;
const int maxn=1e3+7;
int n,V;
int dp[maxn],v[maxn],w[maxn];
int main()
{
scanf("%d %d",&n,&V);
for(int i=1;i<=n;++i) scanf("%d %d",&v[i],&w[i]);
for(int i=1;i<=n;++i)
for(int j=v[i];j<=V;++j)
if(dp[j-v[i]]+w[i]>dp[j])
dp[j]=dp[j-v[i]]+w[i];
printf("%d\n",dp[V]);
return 0;
}
Piggy-Bank
Piggy-Bank
题目大意
给个装有钱的存钱罐,在不打破的情况下求存钱罐存的最小金额。
输入格式
输入一个
t
t
t,表示有
t
t
t 组数据。每组数据输入输入两个数
E
E
E 和
F
F
F
(
1
<
=
E
<
=
F
<
=
10000
)
(1 <= E <= F <= 10000)
(1<=E<=F<=10000),分别表示空的存钱罐重量和装满钱的存钱罐重量,接着输入一个
n
n
n
(
1
<
=
N
<
=
500
)
(1 <= N <= 500)
(1<=N<=500) 表示有
n
n
n 种种类的货币,接下来
n
n
n 行,每行 输入两个数
P
P
P 和
W
W
W
(
1
<
=
P
<
=
50000
,
1
<
=
W
<
=
10000
)
(1 <= P <= 50000, 1 <= W <=10000)
(1<=P<=50000,1<=W<=10000),分别表示每种货币的 面值和重量。
输出格式
每组数据如果凑不出重量为
F
−
E
F - E
F−E 的货币,就输出 “This is impossible.”,否则就输出 “The minimum amount of money in the piggy-bank is X”.
X
X
X 表示凑出的最小价值。
输入样例
2
10 110
2
1 1
50 30
1 6
2
10 3
20 4
输出样例
The minimum amount of money in the piggy-bank is 100.
This is impossible.
#include <cstdio>
#include <cstring>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1e4+7;
int dp[maxn],v[maxn],w[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int e,f,n;
scanf(" %d %d %d",&e,&f,&n);
for(int i=1;i<=n;i++) scanf(" %d %d",&v[i],&w[i]);
int W=f-e;
for(int i=0;i<=maxn;i++) dp[i]=inf;
dp[0]=0;
for(int i=1;i<=n;i++)
for(int j=w[i];j<=W;j++)
if(dp[j-w[i]]+v[i]<dp[j])
dp[j]=dp[j-w[i]]+v[i];
// for(int i=1;i<=W;i++) printf("%d ",dp[i]);puts("");
if(dp[W] != inf) printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);
else puts("This is impossible.");
}
return 0;
}
多重背包
有 n n n 种物品和一个容量为 v v v 的背包,第 i i i 件物品最多有 n [ i ] n[i] n[i] 件可以用,每件费用是 c [ i ] c[i] c[i],价值是 w [ i ] w[i] w[i] 。求解在不超过背包体积的情况下,能够获得的最大价值总和是多少?
最朴素的状态转移方程和完全背包一样
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − k ∗ c [ i ] ] + k ∗ w [ i ] ) f[i][j] = max(f[i-1][j],f[i-1][j-k*c[i]]+k*w[i]) f[i][j]=max(f[i−1][j],f[i−1][j−k∗c[i]]+k∗w[i]) ( 0 ≤ k ≤ n [ i ] ) (0 \leq k \leq n[i]) (0≤k≤n[i])
复杂度是 O ( V ∗ ∑ n [ i ] ) O(V* \sum n[i]) O(V∗∑n[i])
转化成 01 背包求解:把第 i i i 种物品换成 n [ i ] n[i] n[i] 件 01 背包中的物品,则得到了物品为 ∑ n [ i ] \sum n[i] ∑n[i] 的 01 背包问题,复杂度依旧是 O ( V ∗ ∑ n [ i ] ) O(V* \sum n[i]) O(V∗∑n[i])
考虑把第 i i i 种物品换成若干件物品,使得原问题中第 i i i 种物品可取的每种策略都变成 0... n [ i ] 0 ... n[i] 0...n[i] 件 一一 均能等价取若干件代换以后的物品。另外,取超过 n [ i ] n[i] n[i] 件的策略必不能出现。
方法:将第 i i i 件物品拆分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数,使这些系数分别为 1,2,4 ⋯ \cdots ⋯, 2 k − 1 2^{k-1} 2k−1, n [ i ] − 2 k + 1 n[i]-2^{k}+1 n[i]−2k+1,且 k k k 满足 n [ i ] − 2 k + 1 > 0 n[i] - 2^k+1>0 n[i]−2k+1>0 的最大整数。例如,如果 n [ i ] n[i] n[i] 为 13,就将这种物品分成系数分别为 1,2,4,6 的四件物品。复杂度是 O ( V ∗ ∑ l o g n [ i ] ) O(V* \sum logn[i]) O(V∗∑logn[i])
将
n
[
i
]
n[i]
n[i] 拆成 1,2,4,
⋯
\cdots
⋯,
n
[
i
]
−
2
k
+
1
n[i]-2^k+1
n[i]−2k+1。(
k
k
k 是满足
n
[
i
]
−
2
k
+
1
>
0
n[i]-2^k+1>0
n[i]−2k+1>0 的最大整数)
1) 1+2+4+
⋯
\cdots
⋯ +
2
k
−
1
2^{k-1}
2k−1 +
n
[
i
]
n[i]
n[i]-
2
k
2^k
2k+1=
n
[
i
]
n[i]
n[i],保证了最多有
n
[
i
]
n[i]
n[i] 个物品。
2) 1,2,4
⋯
\cdots
⋯
2
k
−
1
2^{k-1}
2k−1 可以凑出 1 到
2
k
2^k
2k-1的所有整数( 联系二进制拆分即可证明 )
3)
2
k
2^k
2k
⋯
\cdots
⋯
n
[
i
]
n[i]
n[i] 的所有整数可以用若干个上述元素凑出(理解成凑
n
[
i
]
−
t
n[i] - t
n[i]−t,而
n
[
i
]
n[i]
n[i] 为上面所有数的和,
t
t
t 则是一个小于
2
k
2^k
2k 的数,那么所有的数中去掉组成
2
k
2^k
2k 的那些数剩下的就可以组成
n
[
i
]
−
t
n[i] - t
n[i]−t)
珍惜现在,感恩生活
珍惜现在,感恩生活
题目大意
假设你一共有资金
n
n
n 元,而市场有
m
m
m 种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?
输入格式
输入数据首先包含一个正整数
C
C
C ,表示有
C
C
C 组测试用例,每组测试用例的第一行是两个整数
n
n
n 和
m
m
m
(
1
<
=
n
<
=
100
,
1
<
=
m
<
=
100
)
(1<=n<=100, 1<=m<=100)
(1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是
m
m
m 行数据,每行包含 3 个数
p
p
p,
h
h
h 和
c
(
1
<
=
p
<
=
20
,
1
<
=
h
<
=
200
,
1
<
=
c
<
=
20
)
c(1<=p<=20,1<=h<=200,1<=c<=20)
c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。
输出格式
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。
输入样例
1
8 2
2 100 4
4 100 2
输出样例
400
01 背包做法
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int n,V;
int v[maxn],w[maxn],num[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&V,&n);
int dp[maxn];
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i) scanf("%d %d %d",&v[i],&w[i],&num[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=num[i];++j)
for(int k=V;k>=v[i];k--)
if(dp[k-v[i]]+w[i]>dp[k])
dp[k]=dp[k-v[i]]+w[i];
printf("%d\n",dp[V]);
}
return 0;
}
二进制拆分
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int n,V;
int v[maxn],w[maxn],num[maxn];
int dp[maxn],vv[maxn],ww[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&V,&n);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i) scanf("%d %d %d",&v[i],&w[i],&num[i]);
int total=0;
for(int i=1;i<=n;++i) {
for(int j=1;j<num[i];j<<=1) {//二进制拆分
vv[++total]=j*v[i];//存容量
ww[total]=j*w[i];//存价值
num[i]-=j;
}
if(num[i]) { //当num[i]>0
vv[++total]=num[i]*v[i];
ww[total]=num[i]*w[i];
}
}
for(int i=1;i<=total;++i)//01背包
for(int j=V;j>=vv[i];--j)
if(dp[j-vv[i]]+ww[i]>dp[j]) dp[j]=dp[j-vv[i]]+ww[i];
printf("%d\n",dp[V]);
}
return 0;
}
单调队列是指一个队列内部的元素具有严格单调性的一种数据结构,分成单调递增和单调递减队列。单调队列满足以下性质:
(1)单调队列必须满足从队首到队尾的严格单调性;
(2)排在队列前面比排在队列后面的要先进队。
单调队列问题就是滑动窗口问题,维持一个单调窗口,元素进队列将元素与队尾元素进行比较,在维持单调递增的情况下,如果该元素大于队尾元素就入队,反之队尾元素出队列,直到满足该元素大于队尾元素。
单调队列优化
题解:
对于一种物品而言,存在v种情况。假设前面已经选择了1、2、3 …… v v v
对同一种情况来说,它是一个等差数列,公差是v。
f [ j ] f[j] f[j] : 对于余数为j的情况。 f [ j ] = m a x ( f [ j − v ] + w , f [ j − 2 ∗ v ] + 2 ∗ w . . . . . f [ j − k ∗ v ] + k ∗ w ) f[j] = max(f[j-v]+w,f[j-2*v]+2*w ..... f[j-k*v]+k*w) f[j]=max(f[j−v]+w,f[j−2∗v]+2∗w.....f[j−k∗v]+k∗w);
(此处的0、 v 、 2 v v、2v v、2v 表示的是背包剩余的体积。在同种物品的情况下,剩余的体积越小,其价值越大。但是记录的时候是相反的,也就是说f[j]记录下来的是背包存在体积为j的物品时,所拥有价值)
f [ 0 ] f[0] f[0]
f [ v ] − 1 ∗ w f[v] - 1 * w f[v]−1∗w
f [ 2 v ] − 2 ∗ w f[2v] - 2 * w f[2v]−2∗w
…
f [ k v ] − k ∗ w f[kv] - k * w f[kv]−k∗w :在余数为0里面的第 k k k 个数
f [ i ] f[i] f[i] 只与 f [ i − 1 ] f[i-1] f[i−1] 有关,因此多声明一个数组来存前一个的情况
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20010;
int n, V;
int dp[N];
int q[N];
int g[N];//由于dp 数组的计算公式是 max(dp[i-1][j],dp[i-1][j-v],...),g存储的是前一个状态(i-1)的值
int main()
{
int t;
scanf("%d",&t);
while(t--) {
memset(dp,0,sizeof dp);
scanf("%d%d",&V,&n);
for (int i = 0; i < n; i ++ ) {
int v, w, s;
scanf("%d%d%d",&v,&w,&s);
memcpy(g, dp, sizeof dp);
for (int j = 0; j < v; j ++ ) { //取余,分组。
int hh = 0, tt = -1;
for (int k = j; k <= V; k += v) { //遍历组内情况
if (hh <= tt && q[hh] < k - s * v) hh ++ ; // 出队列
//当其超出使用次数时,由于是单调递增数列,所以后面的一定比前面的值要大
//限制队列长度
while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
//比较当前状态的最大值,**这里就是为什么后面队列尾部元素要减k*w的原因。**
//(因为你放到余数为j的第k个数是队列里面最大的数+新增(k-q[head])/v个物品后的价值)
q[ ++ tt] = k;
dp[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n",dp[V]);
}
return 0;
}
滑动窗口问题
Sliding Window
题目大意
给个长度为
n
n
n
(
n
≤
1
e
6
)
(n \leq 1e^6)
(n≤1e6) 的数组,假设有个长度为
k
k
k 的滑动窗口从数组的最左边一直滑到最右边。只能在窗口中看到k个数字。每次滑动窗口向右移动一个位置。
假设数组为[1 3 -1 -3 5 3 6 7],k为3。
窗口位置 | 最小值 | 最大值 |
---|---|---|
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。 | ||
输入格式 | ||
输入包含两行。 | ||
第一行包含两个整数 n n n 和 k k k,分别代表数组长度和滑动窗口的长度。 | ||
第二行有 n n n 个整数,代表数组的具体数值。 | ||
同行数据之间用空格隔开。 | ||
输出格式 | ||
输出包含两个。 | ||
第一行输出,从左至右,每个位置滑动窗口中的最小值。 | ||
第二行输出,从左至右,每个位置滑动窗口中的最大值。 |
#include <cstdio>
#define ll long long
using namespace std;
const int maxn = 1e6 + 7;
int n, k;
int a[maxn], q[maxn];
int main()
{
scanf("%d%d",&n,&k);
for(int i = 0; i < n; i++) scanf("%d",&a[i]);
int head = 0, tail = -1;
for(int i = 0; i < n; i++)
{
if(head <= tail && q[head] < i - k + 1) head++;
while(head <= tail && a[q[tail]] >= a[i]) tail--;
q[++tail] = i;
if(i >= k - 1) printf("%d ",a[q[head]]);
}
puts("");
head = 0, tail = -1;
for(int i = 0; i < n; i++)
{
if(head <= tail && q[head] < i - k + 1) head++;
while(head <= tail && a[q[tail]] <= a[i]) tail--;
q[++tail] = i;
if(i >= k - 1) printf("%d ",a[q[head]]);
}
puts("");
return 0;
}
二维费用的背包问题
对于每件物品具有两种不同的费用,选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出最大值(背包容量) 。问怎样选择物品可以得到最大的价值呢?
设这两种代价分别为代价 1 和代价 2,第 i i i 件物品所需的两种代价分别为 a i a_i ai 和 b i b_i bi 。这两种代价可付出的最大值( 两种背包容量 ) 分别为 v v v 和 u u u。物品的价值为 w i w_i wi 。
设 f [ i ] [ v ] [ u ] f[i][v][u] f[i][v][u] 表示前 i i i 件物品付出两种代价分别为 v v v 和 u u u 时可获得的最大价值。状态转移方程: f [ i ] [ v ] [ u ] = m a x ( f [ i − 1 ] [ v ] [ u ] , f [ i − 1 ] [ v − a [ i ] ] [ u − b [ i ] ] + w [ i ] ) f[i][v][u] = max(f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]) f[i][v][u]=max(f[i−1][v][u],f[i−1][v−a[i]][u−b[i]]+w[i])
二维费用的背包问题
题目大意
有
N
N
N 件物品和一个容量是
V
V
V 的背包,背包能承受的最大重量是
M
M
M。每件物品只能用一次。体积是
v
i
v_i
vi,重量是
m
i
m_i
mi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大,输出最大价值。
输入格式
第一行两个整数,
N
N
N,
V
V
V,
M
M
M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有
N
N
N 行,每行三个整数
v
i
,
m
i
,
w
i
v_i,m_i,w_i
vi,mi,wi,用空格隔开,分别表示第
i
i
i 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围:
0<
N
N
N≤1000;0<
V
,
M
V,M
V,M≤100
0<
v
i
,
m
i
v_i,m_i
vi,mi≤100;0<
w
i
w_i
wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例
8
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int n,v,m;
int x,y,z;
int f[maxn][maxn];
int main()
{
scanf("%d%d%d",&n,&v,&m);
while(n--)
{
scanf("%d%d%d",&x,&y,&z);
for(int i=v;i>=x;i--)
for(int j=m;j>=y;j--)
if(f[i-x][j-y]+z>f[i][j])
f[i][j]=f[i-x][j-y]+z;
}
printf("%d\n",f[v][m]);
return 0;
}
分组背包
有 N N N 件物品和一个容量为 v v v 的背包,第 i i i 件物品的费用是 c i c_i ci,价值是 w i w_i wi。这些物品被划分为若干组,每组中的物品互相冲突,最多选择一件。求解将哪些物品装入背包可使这些物品的费用总和在不超过背包容量的前提下,获得的最大价值总和是多少?
1.确定状态
f
[
k
]
[
v
]
f[k][v]
f[k][v] 表示前
k
k
k 组物品花费费用
v
v
v 能取得最大值。
2.确定状态转移方程
f
[
k
]
[
v
]
=
m
a
x
(
f
[
k
−
1
]
[
v
]
,
f
[
k
−
1
]
[
v
−
c
[
i
]
]
+
w
[
i
]
)
f[k][v]=max(f[k-1][v],f[k-1][v-c[i]]+w[i])
f[k][v]=max(f[k−1][v],f[k−1][v−c[i]]+w[i]) 物品
i
i
i 属于组
k
k
k。
for所有的组 k
for v=v ... 0
for所有的 i 属于组 k
f[v]=max(f[v],f[v-c[i]]+w[i]);
"for v=v ... 0" 这层循环必须在 "for所有的 i 属于组 k"之外,这样才能保证每一组内的物品最多只有一个会被添加到背包中。
分组背包问题
题目大意
有
N
N
N 组物品和一个容量是
V
V
V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是
v
i
j
v_{ij}
vij,价值是
w
i
j
w_{ij}
wij,其中
i
i
i 是组号,
j
j
j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大,输出最大价值。
输入格式
第一行有两个整数
N
N
N,
V
V
V,用空格隔开,分别表示物品组数和背包容量。接下来有
N
N
N 组数据:
每组数据第一行有一个整数
S
i
S_i
Si,表示第
i
i
i 个物品组的物品数量;
每组数据接下来有
S
i
S_i
Si 行,每行有两个整数
v
i
j
,
w
i
j
v_{ij},w_{ij}
vij,wij,用空格隔开,分别表示第
i
i
i 个物品组的第
j
j
j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围: 0<
N
,
V
N,V
N,V≤100,0<
S
i
S_i
Si≤100,0<
v
i
j
,
w
i
j
v_{ij},w_{ij}
vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例
8
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1e3+7;
int N,V;
int v[maxn][maxn],w[maxn][maxn],num[maxn];
int dp[maxn];
int main()
{
scanf("%d %d",&N,&V);
memset(dp,0xcf,sizeof(dp)); //-INF
for(int i=1;i<=N;++i) {
scanf("%d",&num[i]);
for(int j=1;j<=num[i];++j)
scanf("%d %d",&v[i][j],&w[i][j]);
}
dp[0]=0;
for(int i=1;i<=N;++i)//01背包
for(int j=V;j>=0;--j)
for(int k=1;k<=num[i];++k)
if(j>=v[i][k]&&dp[j-v[i][k]]+w[i][k]>dp[j]) dp[j]=dp[j-v[i][k]]+w[i][k];
int ans=0;
for(int i=0;i<=V;++i)
if(dp[i]>ans) ans=dp[i];
printf("%d\n",ans);
return 0;
}