当前位置: 首页 > news >正文

P1722 矩阵 II 题解 DFS深度优先遍历与卡特兰数(Catalan number)解

### 题目理解与修改意见:

题目要求我们求解有多少种方式能够在一个 `1 × 2n` 的矩阵中放置红色和黑色算筹,使得矩阵满足“平衡”条件。具体来说,要求:

1. 红色算筹和黑色算筹的数量必须相等。
2. 在前 `i` 格中,红色算筹的数量必须大于或等于黑色算筹的数量。

#### 思路分析:

思路一: dfs 深度优先算法

我们可以将问题视为一种 **合法的括号序列问题**,其中:

* 红色算筹(`r`)充当“左括号”(`(`),
* 黑色算筹(`b`)充当“右括号”(`)`)。

为了保持平衡,我们需要保证在任何前缀中,红色算筹的数量不小于黑色算筹的数量,这正是合法括号序列的条件。最终的合法序列长度为 `2n`,其中红色算筹与黑色算筹数量相等。

#### 关键点:

1. **递归转移**:每次我们可以选择放置红色算筹或者黑色算筹,在放置黑色算筹时,必须保证红色算筹数量大于或等于黑色算筹。
2. **递归中的计数**:使用 DFS 来枚举所有可能的排列,通过动态规划缓存结果,避免重复计算。

#### 问题中的现有代码分析:

* **递归函数 `dfs(m, cnt)`**:递归枚举当前放置的算筹,`m` 是当前的位置,`cnt` 是当前放置的红色算筹的数量。
* **状态转移**:`r++` 表示放置一个红色算筹,`b++` 表示放置一个黑色算筹,满足条件的情况下递归调用。

#### 修改与改进:

根据题目要求,需要根据“红色算筹和黑色算筹数量相等且红色算筹数量不小于黑色算筹数量”的条件来修改现有代码,下面是改进后的实现:

```cpp
#include <bits/stdc++.h>
using namespace std;

int n, v[205][205], r, b;
const int MOD = 100;

int dfs(int m, int cnt) {
// m 代表当前的位置,cnt 代表已经放置的红色算筹的数量
if (m == 2 * n) {  // 如果已经放置了 2n 个算筹,说明完成了一个合法的排列
return (cnt == n);  // 只有当红色算筹数量等于黑色算筹数量时,才是有效的
}

    if (v[m][cnt] != -1) {  // 如果已经计算过,直接返回结果
return v[m][cnt];
}

    v[m][cnt] = 0;  // 初始化

    // 放置红色算筹
if (r < n) {
r++;
v[m][cnt] = (v[m][cnt] + dfs(m + 1, cnt + 1)) % MOD;
r--;
}

    // 放置黑色算筹,条件是红色算筹数量大于等于黑色算筹数量
if (b < n && cnt > b) {
b++;
v[m][cnt] = (v[m][cnt] + dfs(m + 1, cnt)) % MOD;
b--;
}

    return v[m][cnt];
}

int main() {
cin >> n;
memset(v, -1, sizeof(v));  // 初始化缓存数组
cout << dfs(0, 0) << endl;  // 从第 0 个位置开始,已放置的红色算筹数量为 0
return 0;
}
```

### 主要修改与改进:

1. **状态缓存**:`v[m][cnt]` 被初始化为 `-1` 表示尚未计算过,用于动态规划缓存。
2. **放置红色算筹条件**:当 `r < n` 时可以放置红色算筹。
3. **放置黑色算筹条件**:当 `b < n` 且当前黑色算筹数量 `b` 小于红色算筹数量 `cnt` 时可以放置黑色算筹。
4. **终止条件**:当 `m == 2 * n` 时,如果红色和黑色算筹的数量相等,即 `cnt == n`,说明这是一个有效的排列。

### 时间复杂度分析:

* 每个递归状态 `v[m][cnt]` 只会计算一次,因此总体时间复杂度为 O(n^2)。

### 输入输出示例:

#### 输入:

```
2
```

#### 输出:

```
2
```

### 方案解释:

1. 红、黑、红、黑
2. 红、红、黑、黑

这些方案是合法的,满足红色算筹的数量大于等于黑色算筹的数量,最终方案数量对 100 取模后的结果为 `2`。

思路二: 卡特兰数(Catalan number)数列

这个问题可以使用卡特兰数来解决。卡特兰数(Catalan number)是一个数列,它在组合数学中有许多应用,其中一种应用就是计算在给定的条件下,红色和黑色算筹的排列方式。

对于这个问题,我们需要找到一个 1×2n 的矩阵中,红色和黑色算筹数量相等且任意前缀中红色算筹数量不小于黑色算筹数量的排列方式。这正好是卡特兰数的一个经典应用。

第 n 个卡特兰数 \( C_n \) 的公式为:
\[ C_n = \frac{1}{n+1} \binom{2n}{n} \]

我们需要计算 \( C_n \) 并对 100 取模。

### C++ 代码实现

```cpp
#include <iostream>
#include <vector>

using namespace std;

int catalan(int n) {
vector<int> C(n + 1, 0);
C[0] = 1;
C[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 0; j < i; j++) {
C[i] = (C[i] + C[j] * C[i - j - 1]) % 100;
}
}
return C[n];
}

int main() {
int n;
cin >> n;
cout << catalan(n) << endl;
return 0;
}
```

### 代码解释

1. **定义卡特兰数数组**:我们使用一个动态规划数组 `C` 来存储卡特兰数,其中 `C[i]` 表示第 i 个卡特兰数。
2. **初始化**:`C[0]` 和 `C[1]` 都初始化为 1,因为 \( C_0 = 1 \) 和 \( C_1 = 1 \)。
3. **动态规划计算**:使用两层循环来计算 `C[i]`,其中外层循环从 2 到 n,内层循环从 0 到 i-1。根据卡特兰数的递推公式 \( C_i = \sum_{j=0}^{i-1} C_j C_{i-j-1} \),我们更新 `C[i]`。
4. **取模**:在计算过程中,我们对 100 取模,以确保结果在 100 以内。
5. **输出结果**:最后输出 `C[n]`,即第 n 个卡特兰数对 100 取模后的结果。

### 样例验证

对于输入 \( n = 2 \):
- `C[0] = 1`
- `C[1] = 1`
- `C[2] = C[0] * C[1] + C[1] * C[0] = 1 * 1 + 1 * 1 = 2`

因此,输出为 2,与题目中的样例一致。

### 时间复杂度

该算法的时间复杂度为 \( O(n^2) \),因为我们需要计算 \( C[i] \) 时,内层循环需要遍历 i 次。

### 空间复杂度

空间复杂度为 \( O(n) \),因为我们使用了一个大小为 \( n+1 \) 的数组来存储卡特兰数。

http://www.dtcms.com/a/272793.html

相关文章:

  • Spring Boot+Redis+Caffeine 二级缓存架构的终极实现方案、包含万级QPS下的黄金配置参数、全文超过2500字(博君一赞)
  • XGBoosting算法详解(Boosting思想的代表算法)
  • C语言<数据结构-链表>
  • LangChain RAG 实战
  • Transformers 和 PyTorch 的区别与安装指南
  • Docker 高级管理--Dockerfile镜像制作
  • Context Engineering Framework 系统详细介绍
  • 链表算法之【合并两个有序链表】
  • 牛客笔试题 除2!
  • 读取按键的四种方式
  • IMU误差模型
  • 显卡GPU的架构和工作原理
  • 输入框过滤选项列表,el-checkbox-group单选
  • JDK 1.7 vs JDK 1.8
  • 为什么域名加端口访问需要放行端口?
  • 【算法训练营Day11】二叉树part1
  • c语言初阶 指针
  • CH9121T电路及配置详解
  • 【算法笔记 day three】滑动窗口(其他类型)
  • Spring Security 技术原理与实战全景详解
  • 【OD机试题解法笔记】根据IP查找城市
  • 观众信息设置与统计(视频高级分析与统计功能)
  • 身份认证缺陷
  • Gulp实现功能及插件总结
  • java并发包下CountDownLatch、Semaphore用法
  • 【牛客刷题】活动安排
  • i.mx8 网络速率测试
  • Transformer:自注意力驱动的神经网络革命引擎
  • 网络综合实验
  • Linux中gdb使用