前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >蓝桥杯练习题总结(三)线性dp题(摆花、数字三角形加强版)

蓝桥杯练习题总结(三)线性dp题(摆花、数字三角形加强版)

作者头像
走在努力路上的自己
发布2024-03-26 08:36:11
910
发布2024-03-26 08:36:11
举报
文章被收录于专栏:走在努力路上的自己

一、摆花

题目描述 小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过αi盆。摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。试编程计算,一共有多少种不同的摆花方案。 输入描述 第一行包含两个正整数n和m,中间用一个空格隔开。 第二行有n个整数,每两个整数之间用一个空格隔开,依次表示a1、a2、...、an。 其中,0 < n ≤ 100,0 < m ≤ 100,0 ≤ ai ≤ 100。 输出描述 输出只有一行,一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对10^9 + 7取模的结果。 输入样例 2 4 3 2 输出样例 2

解题思路

思路一:

确定状态:

首先,需要明确问题的要求:给定n种不同的花和每种花的最大数量限制,求出在摆放m盆花时,能够形成的不同摆花方案数。这个问题的关键在于每种花可以选择摆放的数量从0到其最大限制,且摆放的花必须按照花的种类顺序排列。

在动态规划中,定义状态是至关重要的一步。这里,我们定义状态dp[i][j]为考虑前i种花时,摆放j盆花的不同方案数。

代码语言:javascript
复制
int n, m; cin >> n >> m;
vector<int>a(n + 1);
vector<vector<int> >dp(101, vector<int>(101));
初始化:

初始化第一种花的情况,因为只有一种花,所以可以从0到a[1]朵任意选择,都只有一种方式

代码语言:javascript
复制
for (int i = 0; i <= a[1]; i++) dp[1][i] = 1;
  • 外层循环遍历花的种类:从1到n,(花的种类为0时情况已初始化)对每种花进行遍历。
  • 中层循环遍历目标花的总数:从0到m,对可能摆放的花的总数进行遍历。
  • 在内层循环中,再加一个循环遍历当前考虑的这种花可以选择的数量(从0到该种花的数量上限或剩余可摆放数量的较小值),这里通过检查j - k >= 0来确保不会有负数的情况发生。
代码语言:javascript
复制
 for (int i = 2; i <= n; i++) { // 遍历每一种花 
        for (int j = 0; j <= m; j++) { // 遍历当前要选的花的总数
            for (int k = 0; k <= a[i] && j - k >= 0; k++) {
                    ......
            }
        }
    }
  • 状态转移方程dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % p;的含义是,要得到前i种花中摆放j盆花的方案数,需要将所有可能包含第i种花的数量(从0到a[i])的方案数加起来。每次更新dp[i][j]时,都要对p取模,以避免整数溢出并满足题目要求。
代码语言:javascript
复制
dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % p;
代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;
const int N = 200, p = 1e6 + 7;
int dp[N][N], n, m, a[N];

int main()
{
    cin >> n >> m;// 输入花的种类数n和目标数m
    for (int i = 1; i <= n; i++) cin >> a[i];
    // 输入每种花的数量
    for (int i = 0; i <= a[1]; i++) dp[1][i] = 1;
    // 初始化第一种花的情况,因为只有一种花,所以可以从0到a[1]朵任意选择,都只有一种方式

    // 动态规划填表过程
    for (int i = 2; i <= n; i++) { // 遍历每一种花 
        for (int j = 0; j <= m; j++) { // 遍历当前要选的花的总数
            for (int k = 0; k <= a[i] && j - k >= 0; k++) {
            // 状态转移方程:dp[i][j]表示前i种花选出j朵的方式数,它等于前i - 1种花选出j - k朵的方式数之和
                dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % p;
            }
        }
    }
    cout << dp[n][m];// 输出结果,即前n种花选出m朵的方式数(模p意义下)
    return 0;
}

思路二:

确定状态:

首先,需要明确问题的要求:给定n种不同的花和每种花的最大数量限制,求出在摆放m盆花时,能够形成的不同摆花方案数。这个问题的关键在于每种花可以选择摆放的数量从0到其最大限制,且摆放的花必须按照花的种类顺序排列。

在动态规划中,定义状态是至关重要的一步。这里,我们定义状态dp[i][j]为考虑前i种花时,摆放j盆花的不同方案数。

代码语言:javascript
复制
int n, m; cin >> n >> m;
vector<int>a(n + 1);
vector<vector<int> >dp(101, vector<int>(101));
初始化:

对于本问题,我们知道不摆放任何花(即j=0时)只有一种方案,即什么花都不摆。因此,初始化dp[0][0] = 1,表示没有花时,摆放0盆花的方案数为1。其他情况(即当j>0时),在没有考虑任何花的情况下是不可能摆放任何花的,这些状态默认为0,反映了不可能发生的情况。

代码语言:javascript
复制
dp[0][0] = 1;
循环遍历:
  • 外层循环遍历花的种类:从1到n,(花的种类为0时情况已初始化)对每种花进行遍历。
  • 中层循环遍历目标花的总数:从0到m,对可能摆放的花的总数进行遍历。
  • 内层循环遍历当前种类花的可能数量:从0到当前种类花的数量限制或j中的较小值(因为不可能摆放超过总数j的花)。这一步是优化的关键,通过只遍历到min(a[i], j)来减少不必要的计算。
代码语言:javascript
复制
for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= m; j++)
		{
			for (int k = 0; k <= min(a[i],j); k++)
			{
                ......
			}
		}
	}
状态转移方程:
代码语言:javascript
复制
dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % p;
代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;
const int p = 1e6 + 7;

int main()
{
	int n, m; cin >> n >> m;
	vector<int>a(n + 1);
	vector<vector<int> >dp(101, vector<int>(101));
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	dp[0][0] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= m; j++)
		{
			for (int k = 0; k <= min(a[i],j); k++)
			{
				dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % p;
			}
		}
	}
	cout << dp[n][m] % p;
}

二、数字三角形加强版

数字三角形最大路径和问题 给定一个数字三角形,从三角形的顶部到底部有多条不同的路径。对于每条路径,把路径上的数加起来可以得到一个和。任务是找到最大的和。 路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右边的那个数。此外,向左下走的次数与向右下走的次数相差不能超过1。 输入描述: 输入的第一行包含一个整数N(1≤N≤100),表示三角形的行数。下面的N行给出数字三角形。数字三角形上的数都是0至100之间的整数。 输出描述: 输出一个整数,表示最大路径和。 输入输出样例: 输入: 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 输出: 27

数字三角形

http://t.csdnimg.cn/2IdF4

此题与之前这题的不同点在与多了一个这样的要求:

  • 此外,向左下走的次数与向右下走的次数相差不能超过1。

意为:路径最后会停在最后一行中间的位置。此时有奇数和偶数两种情况,但是可以统一考虑为一种情况:

代码语言:javascript
复制
max(dp[n][(n + 1) / 2], dp[n][(n + 2) / 2]);

如果是奇数,那么两个数值相同;如果是偶数,取更大的一个,皆符合题意。

代码语言:javascript
复制
#include <iostream>
using namespace std;
int a[200][200], dp[200][200], n;
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i; j++)
			cin >> a[i][j];
	dp[1][1] = a[1][1];
	for (int i = 2; i <= n; i++)
		for (int j = 1; j <= i; j++)
			dp[i][j] = a[i][j] + max(dp[i - 1][j], dp[i - 1][j - 1]);
	cout << max(dp[n][(n + 1) / 2], dp[n][(n + 2) / 2]);
	return 0;
}

今天就先到这了!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、摆花
    • 思路一:
      • 确定状态:
      • 初始化:
      • 确定状态:
      • 初始化:
      • 循环遍历:
      • 状态转移方程:
  • 思路二:
  • 二、数字三角形加强版
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档