蓝桥杯训练-3.20

蓝桥杯训练-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[va[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[ij][1]+dp[ij][2]+dp[ij][3]+dp[ij][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[i1][j1]=dp[(i1)(j1)][1]+dp[(i1)(j1)][2]dp[(i1)(j1)][j1]
所以就有:
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[i1][j1]+dp[ij][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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值