洛谷P1077 [NOIP2012 普及组] 摆花 (DP)

本文介绍了一种编程问题,涉及如何在有限花盆数量和花种数量限制下计算不同的摆花方案。通过递推公式和模运算优化,解决了计算组合数的问题,适用于小规模但可能情况众多的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 mm 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n种花,从1到n标号。为了在门口展出更多种花,规定第 ii 种花不能超过ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列.
试编程计算,一共有多少种不同的摆花方案。
思路:

m 和 n 的范围都很小,不用对数字类型做特殊处理。至于如何求解,起码你得先搞懂题意,这个题目有一个对应的模板题目:“求和问题”。把题目翻译过来就是有 n 个数字,每个数字有自己的取值范围,从 0 到 ai , 求取出 n 个数字的和刚好为 m 的组合数。m 和 n 的范围都不大,为什么这个题强调了要对结果取模呢?我们先来分析一下这个问题。 对比一下递推公式的推导过程:
摆花时摆当前的花时,需要在摆放完上一盆花产生的结果中进行累加 由于传球游戏只能从左右两个相邻位置相加,所以数字的范围不会太大。但是摆花时,如果当前的花有 10 盆,那么当前的花摆放 0、1、2…10 产生的都是不同的结果,都需要考虑,所以可能的情况是非常多的,可能的情况多,那种对应的方案数也会大,取模就变得很必要了。
根据上述对题目的分析,递推公式也很显然了,假设状态转移都保存在数组 dp[n][m] 中,第一维表示当前对第 i 种花作分析,第二维表示当前摆放的所有的花盆数量和。递推公式如下:
dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + … dp[i-1][j-ai]
dp[i][j] 的含义是放第 i 种花时,花盆总数刚好是 j ,第 i 种花最多有 ai 盆,所以 dp[i-1] 中只要花盆数量大于 j - ai 的情况都能凑成 dp[i][j]。 有了递推公式,问题就解决了一半了,参考代码如下

#include<bits/stdc++.h>

using namespace std;

const int maxn=10005,mod=1e6+7;
int dp[maxn],a[maxn];
int main()
{
    int cnt=0,i,j,n,m;
    cin>>n>>m;
    for(i=0;i<n;i++)
    {
        cin>>a[i];
    }
    dp[0]=1;
    for(i=0;i<n;i++)
    {
        for(j=m;j>=0;j--)
        {
            for(int k=1;k<=min(a[i],j);k++)
                dp[j]=(dp[j]+dp[j-k])%mod;
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}
<think>好的,我现在需要帮助用户解答关于“P1002 Java”的问题。根据用户提供的引用内容,看起来P1002指的是上的一个题目,具体是过河卒问题。用户可能是在问如何用Java解决这个问题,或者关于该题目的具体实现方法。 首先,我要回顾一下过河卒问题的描述。根据引用[3],题目中棋盘上的A点是(0,0),B点是(n,m),而卒需要从A走到B,且只能向右或向下移动。同时,马的位置及马控制的点是不可通过的。因此,这是一个典型的动态规划问题,需要计算从起点到终点的路径数,避开被马控制的点。 接下来,引用[1]和引用[2]提到了递推式,即DP[i][j] = DP[i-1][j] + DP[i][j-1],这应该是该问题的状态转移方程。也就是说,到达某一点的路径数等于左边和上边点的路径数之和,前提是该点不是被马控制的点。 用户的问题可能集中在如何用Java实现这个动态规划算法。我需要考虑以下几个方面: 1. **输入处理**:读取马的位置和终点坐标。 2. **标记马的控制点**:确定哪些坐标是被马控制的,不可通过。 3. **初始化DP数组**:处理边界条件,比如第一行和第一列,如果在这些边界上遇到被控制的点,后面的点也无法到达。 4. **递推计算**:按照状态转移方程填充DP数组。 5. **输出结果**:DP[n][m]即为答案。 同时,需要注意数组的索引是否越界,比如是否考虑了从0开始还是从1开始的坐标。根据引用[3],题目中A点是(0,0),所以可能需要将坐标整体偏移,避免负数索引。或者,可能在实现时把DP数组定义为(n+1) x (m+1),以包含起点到终点的所有点。 另外,马的控制点有8个方向,需要正确计算这些位置,并检查是否在棋盘范围内。例如,如果马的位置是(x,y),那么被控制的点包括(x±1, y±2)(x±2, y±1)等,8个点。需要确保这些点不超出棋盘边界,否则会引发数组越界错误。 在初始化DP数组时,DP[0][0]应该是1,因为起点只有一种路径(不动)。然后,第一行和第一列的每个点,如果未被控制,则其路径数等于前一个点的路径数(因为只能向右或向下走)。如果在第一行或第一列中某个点被控制,那么其后的点都无法到达,路径数保持为0。 接下来,编写Java代码的大致步骤: - 读取输入n, m, 马的位置x, y。 - 创建一个二维数组dp,大小为(n+1) x (m+1)。 - 创建一个二维布尔数组标记障碍点。 - 计算并标记马的控制点。 - 初始化dp[0][0] = 1,如果起点未被控制。 - 处理第一行和第一列的初始化。 - 填充其他位置的dp值,跳过被控制的点。 - 最后输出dp[n][m]。 需要注意的边界情况包括:当马的位置正好在起点或终点时,路径数为0;当n或m为0时,路径可能只有一条,但需要检查是否被马控制。 现在,可能需要处理坐标的偏移问题,比如题目中的坐标是否允许负数,或者是否需要将坐标整体调整。例如,马的位置可能在棋盘之外,但根据题目描述,应该是给出的位置在棋盘范围内,所以可能需要验证输入的有效性,但题目可能假设输入是合法的。 此外,在Java中,数组索引从0开始,而题目中的坐标也是从0开始,所以可能不需要偏移。例如,B点是(n,m),对应数组的索引就是n和m,只要n和m不超过20,数组的大小设为n+1和m+1即可。 举个例子,假设n=6,m=6,马的位置在(3,3)。那么被控制的点包括(4,5), (5,4), (2,5), (1,4), (4,1), (5,2), (2,1), (1,2)等。需要将这些点标记为障碍。 在初始化dp数组时,第一行dp[0][j]的值取决于左边的点是否可达,且当前点是否未被控制。例如,如果dp[0][0]是1,那么dp[0][1]如果未被控制,就是dp[0][0]的值,即1。如果中间某个点被控制,那么后续的点都为0,因为无法到达。 同样的,第一列dp[i][0]的值也是如此。 然后,对于i>=1和j>=1的位置,如果当前位置未被控制,则dp[i][j] = dp[i-1][j] + dp[i][j-1]。否则,dp[i][j] = 0。 现在,我需要考虑如何正确标记这些被控制的点。例如,给定马的坐标(hx, hy),那么周围的八个点是: (hx + 2, hy + 1) (hx + 2, hy - 1) (hx - 2, hy + 1) (hx - 2, hy - 1) (hx + 1, hy + 2) (hx + 1, hy - 2) (hx - 1, hy + 2) (hx - 1, hy - 2) 需要检查这些点是否在0 <= x <=n 和 0 <= y <=m范围内,如果超出则不标记,否则标记为障碍。 在Java代码中,可以使用一个二维布尔数组blocked[][],初始化为false,然后对于每个方向,计算坐标,如果坐标在合法范围内,则设置blocked[x][y] = true。 同时,还需要将马本身的位置(hx, hy)标记为障碍,因为卒不能走到马的位置。 例如,在代码中: int hx = ...; // 输入的马的x坐标 int hy = ...; // 输入的马的y坐标 然后: blocked[hx][hy] = true; 然后循环八个方向,计算的坐标,并检查是否在0<=x<=n和0<=y<=m范围内,如果是,则标记为true。 然后初始化dp数组: dp[0][0] = blocked[0][0] ? 0 : 1; 然后处理第一行: for (int j = 1; j <= m; j++) { if (!blocked[0][j]) { dp[0][j] = dp[0][j-1]; } else { dp[0][j] = 0; } } 处理第一列: for (int i = 1; i <= n; i++) { if (!blocked[i][0]) { dp[i][0] = dp[i-1][0]; } else { dp[i][0] = 0; } } 然后填充其他位置: for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (!blocked[i][j]) { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } else { dp[i][j] = 0; } } } 最后输出dp[n][m]。 需要注意的是,n和m的最大值是20,所以数组的大小不会太大,不会导致内存问题。 另外,输入的处理可能需要将马的坐标转换为整数,例如,用户输入可能是四个整数:n, m, hx, hy,根据题目描述,例如引用[3]中的说明,B点是(n,m),而马的位置需要给出。 例如,输入可能是: 6 6 3 3 这样,B点是(6,6),马的位置是(3,3)。 现在,用户可能的问题是关于如何用Java实现这个算法,或者遇到某些错误,例如数组越界,或者结果不正确。 此外,可能需要处理当起点或终点被马控制的情况,此时结果直接为0。 综上,我需要将上述思路整理成清晰的步骤,并提供一个正确的Java代码示例,同时回答用户的问题,并生成相关问题。</think>### P1002 [NOIP2002 普及组] 过河卒 Java 实现解析 #### 问题描述 棋盘上,卒从$A(0,0)$出发到$B(n,m)$,只能**向右或向下**移动。马的位置$(hx,hy)$及其控制的**8个点**不可通过。求所有合法路径数[^3]。 #### 算法思路 1. **动态规划定义** 设$dp[i][j]$表示从$(0,0)$到$(i,j)$的路径数。递推公式为: $$ dp[i][j] = \begin{cases} 0 & \text{若}(i,j)\text{被马控制} \\ dp[i-1][j] + dp[i][j-1] & \text{否则} \end{cases} $$ [^2] 2. **边界初始化** - 第一行:$dp[0][j] = dp[0][j-1]$(只能从左边来) - 第一列:$dp[i][0] = dp[i-1][0]$(只能从上方来) 3. **标记障碍点** 马的位置及其控制的8个点需标记为不可达[^1]。 #### Java代码实现 ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int m = sc.nextInt(); int hx = sc.nextInt(); int hy = sc.nextInt(); // 创建棋盘,标记障碍点 boolean[][] blocked = new boolean[n+1][m+1]; int[][] dp = new int[n+1][m+1]; int[] dx = {2, 2, -2, -2, 1, 1, -1, -1}; int[] dy = {1, -1, 1, -1, 2, -2, 2, -2}; // 标记马的位置 blocked[hx][hy] = true; // 标记马的控制点 for (int i = 0; i < 8; i++) { int x = hx + dx[i]; int y = hy + dy[i]; if (x >= 0 && x <= n && y >= 0 && y <= m) { blocked[x][y] = true; } } // 初始化起点 dp[0][0] = blocked[0][0] ? 0 : 1; // 初始化第一行 for (int j = 1; j <= m; j++) { if (!blocked[0][j]) { dp[0][j] = dp[0][j-1]; } } // 初始化第一列 for (int i = 1; i <= n; i++) { if (!blocked[i][0]) { dp[i][0] = dp[i-1][0]; } } // 动态规划递推 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (!blocked[i][j]) { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } System.out.println(dp[n][m]); } } ``` #### 关键点说明 1. **输入处理**:读取棋盘终点$B(n,m)$和马的位置$(hx,hy)$。 2. **障碍标记**:通过方向数组标记马控制的8个点,需判断是否越界。 3. **边界初始化**:若起点被标记,直接输出0。 4. **空间优化**:若棋盘较大,可用滚动数组优化空间至$O(m)$。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值