alg2025/src/7/31/U130826.md
2025-07-31 10:46:15 +08:00

199 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

为了解决这个问题我们需要统计给定字符串中所有合法的子串数量。合法子串需要满足三个条件长度至少为3子串内部不包括边界恰好有一个'*',并且去掉这个'*'后,剩余字符串是一个括号匹配串。
### 方法思路
1. **预处理每个位置的左右'*'位置**:对于每个位置,我们预处理其左边最近和右边最近的'*'的位置。这有助于在枚举中心点(即'*')时,快速确定其左右边界,确保子串内部只有一个'*'。
2. **枚举中心点**:遍历字符串中的每个'*',将其作为子串内部的唯一'*'。
3. **处理左边部分**:对于每个中心点,从中心点左侧开始向左扫描到左边界。在扫描过程中,维护当前子串的平衡值(即括号匹配情况)和最小前缀和(确保过程中没有不匹配的括号)。如果遇到左括号'('且最小前缀和非负,则记录当前平衡值。
4. **处理右边部分**:从中心点右侧开始向右扫描到右边界。同样维护平衡值和最小前缀和。如果遇到右括号')'且最小前缀和等于当前平衡值(即最小前缀和出现在序列末尾),则记录当前平衡值。
5. **统计匹配**对于左边部分记录的每个平衡值x在右边部分查找平衡值为-x的记录并累加匹配数量。
### 解决代码
```cpp
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
int n = s.size();
vector<int> left_star(n, -1);
vector<int> right_star(n, n);
int last = -1;
for (int i = 0; i < n; i++) {
left_star[i] = last;
if (s[i] == '*') {
last = i;
}
}
last = n;
for (int i = n-1; i >= 0; i--) {
right_star[i] = last;
if (s[i] == '*') {
last = i;
}
}
long long ans = 0;
for (int pos = 0; pos < n; pos++) {
if (s[pos] != '*') continue;
int left_bound = (left_star[pos] == -1) ? 0 : left_star[pos] + 1;
int right_bound = (right_star[pos] == n) ? n-1 : right_star[pos] - 1;
map<int, int> left_map;
int cur_left = 0, min_cur_left = 0;
for (int i = pos-1; i >= left_bound; i--) {
int val = (s[i] == '(') ? 1 : -1;
int new_cur = val + cur_left;
int new_min = min(val, val + min_cur_left);
cur_left = new_cur;
min_cur_left = new_min;
if (s[i] == '(' && min_cur_left >= 0) {
left_map[cur_left]++;
}
}
map<int, int> cnt_r;
int cur_r = 0;
int min_cur_r = 0;
for (int j = pos+1; j <= right_bound; j++) {
int val = (s[j] == '(') ? 1 : -1;
cur_r += val;
min_cur_r = min(min_cur_r, cur_r);
if (s[j] == ')' && min_cur_r == cur_r) {
cnt_r[cur_r]++;
}
}
for (const auto& p : left_map) {
int x = p.first;
auto it = cnt_r.find(-x);
if (it != cnt_r.end()) {
ans += p.second * it->second;
}
}
}
cout << ans << '\n';
}
return 0;
}
```
### 代码解释
1. **预处理左右'*'位置**:使用两个数组`left_star`和`right_star`,分别存储每个位置左侧和右侧最近'*'的位置。这有助于在枚举中心点时快速确定左右边界。
2. **枚举中心点**:遍历字符串中的每个'*',将其作为可能的子串内部唯一'*'。
3. **处理左边部分**:从中心点左侧开始向左扫描,维护当前子串的平衡值和最小前缀和。如果遇到左括号'('且最小前缀和非负,则记录当前平衡值。
4. **处理右边部分**:从中心点右侧开始向右扫描,同样维护平衡值和最小前缀和。如果遇到右括号')'且最小前缀和等于当前平衡值,则记录当前平衡值。
5. **统计匹配**:对于左边部分记录的每个平衡值`x`,在右边部分查找平衡值为`-x`的记录。每匹配一次,就累加一次组合数量,最终得到合法子串的总数。
这种方法高效地利用了预处理和动态维护平衡值确保在O(n log n)时间内解决问题,满足题目要求。
### 平衡值和最小前缀和详解
在解决括号匹配问题时,平衡值和最小前缀和是两个关键概念,用于高效地判断括号序列的合法性。下面我将详细解释这两个概念及其在算法中的应用:
#### 1. **平衡值 (Balance Value)**
- **定义**:平衡值表示当前括号序列中左括号与右括号的数量差。具体计算规则:
- 遇到左括号 `(`:平衡值 +1
- 遇到右括号 `)`:平衡值 -1
- **意义**
- 平衡值为正:左括号多于右括号
- 平衡值为零:左右括号完全匹配
- 平衡值为负:右括号多于左括号(非法状态)
- **示例**
- 序列 `(()`:平衡值变化 `0 → 1 → 2 → 1`
- 序列 `())`:平衡值变化 `0 → 1 → 0 → -1`(非法)
#### 2. **最小前缀和 (Minimum Prefix Sum)**
- **定义**:在遍历过程中,记录从起始位置到当前位置的所有平衡值的最小值。
- **意义**
- 检测序列是否在任何时刻出现非法状态(平衡值<0
- 保证整个子串的括号匹配有效性
- **关键性质**
- 最小前缀和 0序列始终合法无右括号多余的情况
- 最小前缀和 < 0序列存在非法位置
### 在算法中的应用
#### 左边部分扫描(向左遍历)
```cpp
for (int i = pos-1; i >= left_bound; i--) {
int val = (s[i] == '(') ? 1 : -1;
cur_left = val + cur_left; // 更新平衡值
min_cur_left = min(val, val + min_cur_left); // 更新最小前缀和
if (s[i] == '(' && min_cur_left >= 0) {
left_map[cur_left]++; // 记录有效左边界
}
}
```
- **操作逻辑**
1. 从星号左侧开始向左扫描
2. 遇到左括号才记录因为合法子串必须以 `(` 开头
3. 仅当 `最小前缀和 ≥ 0` 时记录平衡值保证子串有效性
#### 右边部分扫描(向右遍历)
```cpp
for (int j = pos+1; j <= right_bound; j++) {
int val = (s[j] == '(') ? 1 : -1;
cur_r += val;
min_cur_r = min(min_cur_r, cur_r);
if (s[j] == ')' && min_cur_r == cur_r) {
cnt_r[cur_r]++; // 记录有效右边界
}
}
```
- **操作逻辑**
1. 从星号右侧开始向右扫描
2. 遇到右括号才记录因为合法子串必须以 `)` 结尾
3. 仅当 `最小前缀和 == 当前平衡值` 时记录
- 表明从开始到当前位置未出现非法状态
- 且当前平衡值是最小值无未匹配的右括号
### 匹配原理
- **核心条件**整个子串去掉星号需满足
```
左边平衡值 + 右边平衡值 = 0
```
- **匹配操作**
```cpp
for (const auto& p : left_map) {
int x = p.first;
if (cnt_r.count(-x)) {
ans += p.second * cnt_r[-x];
}
}
```
- **示例**
- 左边记录平衡值 `x = 1`左括号多1个
- 右边需匹配平衡值 `y = -1`右括号多1个
- `x + y = 0` 构成完整匹配
### 算法特点
1. **高效性**
- 每个字符最多被扫描两次/右各一次
- 哈希表操作均摊时间复杂度 O(1)
- 总时间复杂度 O(n)满足 ∑|s| 10 的要求
2. **正确性保障**
- 预处理星号边界确保唯一性
- 平衡值检测保证括号匹配
- 最小前缀和检测排除非法子串
3. **边界处理**
- 星号不在子串边界预处理保证
- 子串长度 3扫描范围控制
通过平衡值和最小前缀和的协同作用算法高效地筛选出所有满足条件的合法子串完美解决了括号匹配与星号位置约束的双重挑战