思路:
-
二分查找:
-
left = 1(最小可能距离),right = L(最大可能距离)。 -
每次取
mid = (left + right) / 2,判断是否可以通过增设 ≤K个路标使得所有相邻路标的距离 ≤mid。
-
-
贪心验证:
-
遍历所有相邻原始路标,计算它们之间的
gap。 -
对于每个
gap,计算需要插入的路标数(gap - 1) / mid。 -
如果总增设数
required ≤ K,则mid可行,尝试更小的mid;否则尝试更大的mid。
-
-
输出答案:
- 最终
ans即为最小的"空旷指数"。
- 最终
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int L, N, K;
cin >> L >> N >> K;
vector<int> markers(N);
for (int i = 0; i < N; i++) {
cin >> markers[i];
}
int left = 1; // 最小可能距离
int right = L; // 最大可能距离
int ans = L;
// 二分查找最小的"空旷指数"
while (left <= right) {
int mid = (left + right) / 2;
int required = 0; // 需要增设的路标数量
// 计算需要增设多少路标才能让所有间隔 ≤ mid
for (int i = 1; i < N; i++) {
int gap = markers[i] - markers[i - 1];
required += (gap - 1) / mid;
}
if (required <= K) {
ans = mid;
right = mid - 1; // 尝试更小的"空旷指数"
} else {
left = mid + 1; // 需要更大的"空旷指数"
}
}
cout << ans << endl;
return 0;
}

思路:
-
backtrack函数:这是递归回溯的核心函数。
-
:
n是目标美味程度,current是当前配料组合,sum是当前组合的总和,index是当前处理的配料索引。 -
当处理完所有10个配料(
index == 10),检查总和是否等于n,如果是,则保存当前组合。 -
对于当前配料,尝试1、2、3克,递归处理下一个配料。通过剪枝条件提前终止无效的递归路径。
-
cpp
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> solutions; // 存储所有解决方案
void backtrack(int n, vector<int>& current, int sum, int index) {
if (index == 10) {
if (sum == n) {
solutions.push_back(current);
}
return;
}
// 尝试1、2、3克
for (int i = 1; i <= 3; ++i) {
if (sum + i > n) continue; // 剪枝:总和超过n,跳过
// 剩下的配料即使全选1克也无法达到n,剪枝
if (sum + i + (10 - index - 1) > n) continue;
current[index] = i;
backtrack(n, current, sum + i, index + 1);
}
}
int main() {
int n;
cin >> n;
vector<int> current(10); // 当前组合
backtrack(n, current, 0, 0);
cout << solutions.size() << endl;
for (const auto& sol : solutions) {
for (int i = 0; i < 10; ++i) {
cout << sol[i] << " ";
}
cout << endl;
}
return 0;
}
正则表达式
基本概念
- 字符组 :用方括号
[]表示,用于匹配方括号内的任意一个字符。例如,[abc]可以匹配a、b或c中的任意一个字符。 - 量词 :用于指定前面的字符或字符组出现的次数。常见的量词有
*(零次或多次)、+(一次或多次)、?(零次或一次)、{n}(恰好n次)、{n,}(至少n次)、{n,m}(n到m次)。例如,a*表示匹配零个或多个a,a{2,4}表示匹配2到4个a。 - 元字符 :具有特殊含义的字符,如
^表示匹配字符串的开头,$表示匹配字符串的结尾,.表示匹配除换行符以外的任意一个字符。例如,^a表示以a开头的字符串,a$表示以a结尾的字符串。 - 转义字符 :用反斜杠
\表示,用于转义元字符,使其失去特殊含义,而表示其本身。例如,\.表示匹配字符.,\\表示匹配字符\。
常用操作
- 匹配 :使用正则表达式来检查一个字符串是否符合特定的模式。例如,判断一个字符串是否是有效的电子邮件地址,可以使用正则表达式
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$。 - 查找 :在一个字符串中查找符合正则表达式模式的子串。例如,在一篇文章中查找所有的电话号码,可以使用正则表达式
\d{3}-\d{8}|\d{4}-\d{7}。 - 替换 :将匹配到的字符串替换为指定的内容。例如,将字符串中的所有数字替换为
*,可以使用正则表达式\d和替换字符串*。 - 分割 :根据正则表达式的模式将字符串分割成多个子串。例如,将一个逗号分隔的字符串分割成数组,可以使用正则表达式
,。
示例
- 匹配手机号码:
^1[3-9]\d{9}$。这个正则表达式表示以1开头,第二位是3到9中的任意一个数字,后面跟着9个数字。 - 匹配身份证号码:
^\d{17}[\dXx]$。表示由17位数字和最后一位数字或X(或x)组成。
| 元字符 | 说明 |
|----------|--------------------------------------|-------------------|
| . | 匹配任意单个字符 (除换行符 \n) |
| ^ | 匹配字符串的开头 |
| $ | 匹配字符串的结尾 |
| * | 匹配前面的字符0次或多次 |
| + | 匹配前面的字符1次或多次 |
| ? | 匹配前面的字符0次或1次 |
| {n} | 匹配前面的字符恰好n次 |
| {n,} | 匹配前面的字符至少n次 |
| {n,m} | 匹配前面的字符n到m次 |
| [...] | 匹配括号内的任意一个字符(字符类) |
| [^...] | 匹配不在括号内的任意字符 |
| ` | ` | 或(匹配左边或右边的模式) |
| \d | 匹配数字 (等价于 [0-9]) |
| \D | 匹配非数字 (等价于 [^0-9]) |
| \w | 匹配字母、数字、下划线 (等价于 [a-zA-Z0-9_]) |
| \W | 匹配非字母、数字、下划线 |
| \s | 匹配空白字符(空格、制表符、换行符等) |
| \S | 匹配非空白字符 |
| \b | 匹配单词边界 |
| \B | 匹配非单词边界 |
3. 正则表达式示例
(1) 匹配数字
| 正则表达式 | 说明 | 匹配示例 |
|---|---|---|
\d+ |
匹配1个或多个数字 | 123, 0, 456 |
\d{3} |
匹配3位数字 | 123, 456 |
\d{2,4} |
匹配2~4位数字 | 12, 123, 1234 |