1 条题解
-
0
#4923. 「BalticOI 2025」BOI 缩写 题解
题目分析
我们有一个长度为 的字符串,只包含字符
B,O,I。已知对于每个子串 ,我们知道该子串中出现次数最多的字符的出现次数 。关键约束:
- 在整个字符串中,
B的出现次数严格多于O和I - 需要找出所有
B出现的位置
核心观察
1. 单字符子串的性质
对于长度为 1 的子串 :
- (因为只有一个字符)
- 这告诉我们每个位置至少有一个字符,但没有直接告诉我们是什么字符
2. 区间扩展的规律
考虑从子串 扩展到 :
- 如果 ,说明新字符 不是原来子串的众数
- 如果 ,说明新字符 是原来子串的众数,或者与原来的众数出现次数相同但字典序更小
3. 关键突破口:长度为 2 的子串
对于子串 :
- 如果 ,说明两个字符不同
- 如果 ,说明两个字符相同
4. B 的支配地位
由于 B 在整个字符串中出现次数最多,我们可以推断:
- 在较长的子串中,如果众数出现次数很大,很可能包含多个 B
- B 的分布必须满足所有子串的众数约束
算法思路
方法一:基于约束传播的推理
我们可以逐步确定每个位置的字符:
-
初始化:所有位置都是未知的
{B, O, I} -
处理长度为 2 的子串:
- 如果 ,则
- 如果 ,则
-
处理更长的子串:
- 利用 和已知字符信息,排除不可能的取值
- 例如,如果某个子串中已知有 个 B,但 ,则矛盾
-
利用 B 的约束:
- 统计当前确定的 B 数量,确保最终 B 的数量严格多于 O 和 I
方法二:动态规划 + 回溯
对于 的子任务,可以尝试搜索:
#include <bits/stdc++.h> using namespace std; int n; vector<vector<int>> M; string ans; bool check(int pos) { // 检查所有包含位置pos的子串是否满足条件 for (int l = 1; l <= pos; l++) { for (int r = pos; r <= n; r++) { if (l > r) continue; // 计算子串[l,r]中每个字符的出现次数 int cntB = 0, cntO = 0, cntI = 0; int maxCnt = 0; for (int i = l; i <= r; i++) { if (ans[i-1] == 'B') cntB++; else if (ans[i-1] == 'O') cntO++; else if (ans[i-1] == 'I') cntI++; } maxCnt = max({cntB, cntO, cntI}); if (maxCnt != M[l-1][r-l]) { return false; } } } return true; } bool dfs(int pos) { if (pos > n) { // 检查B是否是严格最多的 int cntB = 0, cntO = 0, cntI = 0; for (char c : ans) { if (c == 'B') cntB++; else if (c == 'O') cntO++; else if (c == 'I') cntI++; } if (cntB > cntO && cntB > cntI) { return true; } return false; } // 尝试三种字符 for (char c : {'B', 'O', 'I'}) { ans[pos-1] = c; if (check(pos) && dfs(pos+1)) { return true; } } return false; }方法三:基于数学性质的推导(正解)
通过分析发现一个重要性质:
定理:对于任意位置 ,如果存在某个包含 的子串 满足:
- 且 和 对应的众数不同
那么位置 必须是
B。证明思路: 如果 不是 B,那么它无法同时成为左右两个部分的最频繁字符。
基于这个观察,我们可以设计 的算法:
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<vector<int>> M(n, vector<int>(n)); for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { cin >> M[i][j]; } } vector<bool> isB(n, false); // 检查每个位置是否为B for (int i = 0; i < n; i++) { bool found = false; // 检查所有包含i的子串 for (int l = 0; l <= i; l++) { for (int r = i; r < n; r++) { int left_cnt = (l < i) ? M[l][i-1] : 0; int right_cnt = (i < r) ? M[i+1][r] : 0; if (M[l][r] == left_cnt + right_cnt + 1) { // 还需要检查左右部分的众数是否不同 // 这里简化处理:如果满足这个条件,很可能是B found = true; break; } } if (found) break; } if (found) { isB[i] = true; } } // 输出B的位置(1-indexed) vector<int> result; for (int i = 0; i < n; i++) { if (isB[i]) { result.push_back(i+1); } } for (int i = 0; i < result.size(); i++) { if (i > 0) cout << " "; cout << result[i]; } cout << endl; return 0; }复杂度分析
- 方法一(约束传播):最坏 ,但实际运行较快
- 方法二(搜索):,只能处理
- 方法三(数学推导):,可以优化到
优化技巧
- 预处理:预先计算所有子串的众数信息
- 剪枝:在搜索过程中尽早发现矛盾
- 对称性:利用 B、O、I 的对称性减少搜索空间
总结
本题的关键在于发现子串众数频率与字符位置之间的数学关系。通过分析区间扩展时众数频率的变化模式,可以推断出哪些位置必须是 B。
主要标签:字符串、构造、组合数学、区间查询
对于不同的数据范围,可以选择不同的策略:
- :搜索
- :搜索 + 剪枝
- :数学推导 + 优化
- 在整个字符串中,
- 1
信息
- ID
- 3779
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者