蓝桥杯训练-3.20
代码训练
• 装箱问题-DP
问题描述
有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
第一行为一个整数,表示箱子容量;
第二行为一个整数,表示有n个物品;
接下来n行,每行一个整数表示这n个物品的各自体积。
输出格式
一个整数,表示箱子剩余空间。
样例输入
24
6
8
3
12
7
9
7
样例输出
0
思路:
本题的思路是动态规划,设dp[v] 为当容量为v时能放入的物品体积之和的最大值 ,一个物品就可以选择放或者是不放,如果放第i个物品,则为dp[v - a[i]] + a[i],如果不放,则继续保持着dp[v],dp[v]取他们中最大的值,那么状态转移方程为:
d
p
[
v
]
=
m
a
x
(
d
p
[
v
]
,
d
p
[
v
−
a
[
i
]
]
+
a
[
i
]
)
dp[v] = max(dp[v], dp[v - a[i]] + a[i])
dp[v]=max(dp[v],dp[v−a[i]]+a[i])
我们在外层循环循环是取的第i件物品,内层从v开始循环,到a[i]结束,这些就可以遍历所有的情况。所以最后剩余空间最小,也就是容量减去能放入物品体积最大的值。
代码:
#include <bits/stdc++.h>
using namespace std;
int dp[200005];//dp[v]表示当容量为V时可放入的物品的总体积
int a[40];//存着各个物品的体积
int V, n;//V是箱子的容量,n表示一共有n个物品
int main()
{
cin >> V;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
}
for(int i = 1; i <= n; i++)//遍历每一个物品
{
for(int j = V; j >= a[i]; j--)//循环控制箱子的容量V,也是从V容量一步步往后遍历,得到每一容量能放的最大体积
{ //这些都是最终问题的子问题
dp[j] = max(dp[j], dp[j - a[i]] + a[i]);
}
}
cout << V - dp[V] << endl;
return 0;
}
• 数的划分-DP
问题描述
将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5; 1,5,1; 5,1,1;
问有多少种不同的分法。
输入格式
n,k
输出格式
一个整数,即不同的分法
样例输入
7 3
样例输出
4 {四种分法为:1,1,5;1,2,4;1,3,3;2,2,3;}
数据规模和约定
6<n<=200,2<=k<=6
思路:
本题依旧是DP的题目,重点在如何推导出动态方程。
我们设dp[i] [j]表示i这个数被分成j份的所有不同分法,我们可以假设,当我们把j份先全部放上 1,那么问题就变为,将i - j这个数全部分成1份,或者两份,或者三份…或者j份(因为不考虑顺序,所以不用想这几份放在哪个位置上):
d
p
[
i
]
[
j
]
=
d
p
[
i
−
j
]
[
1
]
+
d
p
[
i
−
j
]
[
2
]
+
d
p
[
i
−
j
]
[
3
]
…
…
+
d
p
[
i
−
j
]
[
j
]
dp[i][j] = dp[i - j][1] + dp[i - j][2] + dp[i - j][3]……+dp[i - j][j]
dp[i][j]=dp[i−j][1]+dp[i−j][2]+dp[i−j][3]……+dp[i−j][j]
我们再求一个dp[i - 1] [j - 1]:
d
p
[
i
−
1
]
[
j
−
1
]
=
d
p
[
(
i
−
1
)
−
(
j
−
1
)
]
[
1
]
+
d
p
[
(
i
−
1
)
−
(
j
−
1
)
]
[
2
]
…
…
d
p
[
(
i
−
1
)
−
(
j
−
1
)
]
[
j
−
1
]
dp[i - 1][j - 1] = dp[(i - 1) - (j - 1)][1] + dp[(i - 1)- (j - 1)][2]……dp[(i - 1)-(j-1)][j-1]
dp[i−1][j−1]=dp[(i−1)−(j−1)][1]+dp[(i−1)−(j−1)][2]……dp[(i−1)−(j−1)][j−1]
所以就有:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
d
p
[
i
−
j
]
[
j
]
dp[i][j] = dp[i-1][j-1]+dp[i - j][j]
dp[i][j]=dp[i−1][j−1]+dp[i−j][j]
这便是状态转移方程。其中i > j(划分的种类不得大于数本身)
代码:
#include <bits/stdc++.h>
using namespace std;
int dp[205][7];//数字n被分成k份的分法
int n, k;
int main()
{
cin >> n >> k;
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
dp[i][1] = 1;//所有数分成1份都只有一种情况
}
for(int i = 1; i <= n; i++)
{
for(int j = 2; j <= k; j++)
{
if(i >= j)
dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j];
}
}
cout << dp[n][k] << endl;
}
❕还有一种思路是DFS的思路:
函数为dfs(int num, int k, int now) 分别表示剩余的待分的数,剩余待分的次数,现在选出的数
int dfs(int num, int k, int now)
{
if(k == 1)//所有数只分一次都只有一种情况
return 1;
int sum = 0;//记录次数,需在这里开出,否则会漏选
for(int i = now, i <= num / k; i++)//剪枝
{
sum += dfs(num - i, k - 1, i);
}
return sum;
}