这是一个典型的最小化最大间距的问题,可以使用二分查找来解决。下面是详细的解题思路和步骤:
解题思路
-
问题分析:
- 我们需要在已有路标之间增加新的路标,使得相邻路标之间的最大距离(即“空旷指数”)尽可能小。
- 给定最多能增加的路标数
K
,我们希望找到一种最优的安排,使得相邻路标的最大间距最小。
-
二分查找最小的最大间距:
- 设定一个最大间距的搜索范围 [1,L],其中 L 是公路的总长度。
- 用二分查找的方法,假设某个间距
mid
,检查能否通过增加不超过K
个路标,使得所有相邻路标之间的间距不超过mid
。
-
检查函数的设计:
- 对于每一对相邻的已有路标,如果它们的距离超过
mid
,就需要在这段区间内增加一些路标。 - 计算在每对相邻路标之间,需要增加的路标数量,以使每个间隔不超过
mid
。 - 如果所需的路标数不超过
K
,则当前的mid
是可行的;否则,mid
太小,需要增大间距。
- 对于每一对相邻的已有路标,如果它们的距离超过
-
二分查找过程:
- 如果某个
mid
可行(能满足条件),继续尝试更小的间距,看看是否能进一步优化。 - 如果某个
mid
不可行,增大mid
,直到找到最小的可行间距。
- 如果某个
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 判断在最大间距为 maxDist 的情况下,是否能通过增设不超过 K 个路标,使得所有相邻路标的间距不超过 maxDist
bool canAchieveMaxDist(const vector<int>& positions, int K, int maxDist) {int addedSigns = 0; // 记录需要增加的路标数量for (size_t i = 1; i < positions.size(); ++i) {int dist = positions[i] - positions[i - 1]; // 相邻路标之间的距离// 如果相邻路标之间的距离超过 maxDist,需要在这段间隔中增设路标if (dist > maxDist) {// 计算在当前间隔中需要增加的路标数,确保每段间距不超过 maxDistaddedSigns += (dist - 1) / maxDist;}// 如果所需的新增路标数超过了 K,则说明当前的 maxDist 不可行if (addedSigns > K) return false;}return true; // 如果总的新增路标数不超过 K,说明 maxDist 可行
}int main() {ios::sync_with_stdio(false); // 提升 IO 效率cin.tie(nullptr); // 解除 cin 和 cout 的绑定int L, N, K;cin >> L >> N >> K; // 输入公路总长度 L,已有路标数量 N,和最多可新增的路标数 Kvector<int> positions(N);for (int i = 0; i < N; ++i) {cin >> positions[i]; // 输入路标的位置,按升序排列}int left = 1, right = L; // 二分查找的范围:[1, L]int answer = L; // 初始化答案为 L,即最坏情况下的最大间距// 二分查找,寻找最小的可行最大间距while (left <= right) {int mid = left + (right - left) / 2; // 取中点,作为尝试的最大间距// 检查当前的 mid 值是否可行if (canAchieveMaxDist(positions, K, mid)) {answer = mid; // 如果可行,更新答案为当前的最大间距right = mid - 1; // 继续尝试更小的最大间距} else {left = mid + 1; // 如果不可行,增大最大间距,尝试更大的 mid}}cout << answer << '\n'; // 输出最小的最大间距return 0;
}
#include <bits/stdc++.h>
using namespace std;const int N = 1000000 + 10; // 最大树木数量的常量
int a[N], mid, l, r, n, m; // 定义数组a存储树高,其他变量用于二分查找// 检查函数:判断给定锯片高度 x 是否能满足获取至少 m 米木材的要求
bool check(int x) {int s = 0; // 用于累加当前锯片高度 x 能获得的木材长度for (int i = 1; i <= n; i++) { // 遍历每棵树if (a[i] > x) // 如果当前树高 a[i] 大于 x,高于部分可以切割s += a[i] - x; // 累加这棵树能切割的木材长度if (s >= m) // 如果累加长度已经达到或超过 m,提前返回 truereturn true;}return false; // 遍历所有树后,若未达到 m,返回 false
}int main() {cin >> n >> m; // 输入树的数量 n 和所需木材长度 mfor (int i = 1; i <= n; i++) // 读入每棵树的高度cin >> a[i];l = 0, r = 1e9; // 设置二分查找的初始范围:锯片高度可以是从 0 到 1e9// 二分查找,找到满足至少砍下 m 米木材的最大锯片高度while (l <= r) {mid = (l + r) / 2; // 计算当前中间锯片高度if (check(mid)) // 如果该高度能满足砍木材的需求l = mid + 1; // 继续尝试更高的锯片高度elser = mid - 1; // 否则降低锯片高度}cout << r << endl; // 输出最后确定的最大锯片高度return 0;
}
这题可以直接用递归,但递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多,在竞赛中如果系统栈很小的话,过深的递归会让栈溢出。解决方法是手工模拟递归中栈的操作,将递归转化为非递归算法。 程序中,p相当于堆栈的指针,指向当前拆分的数的位置。sum为已经拆分出来的数的和,如果和小于N,则继续往下拆分,下一个拆分的数不小于前一个拆分的数;如果和等于N,则输出这一组解;如果和大于N,则返回到上一个数,并把上一个数加1后继续查找下一组解。(大炮打蚊子)
#include <bits/stdc++.h>
using namespace std;int Num[100]; // 用于存储当前拆分的数字
int n, sum; // n 是待拆分的数字,sum 是当前拆分的总和// 输出当前拆分的加法式子
void Print(int k) {printf("%d", Num[1]); // 打印第一个数字for (int i = 2; i <= k; i++) // 打印后续的数字,并用 "+" 连接printf("+%d", Num[i]);printf("\n"); // 换行,输出当前拆分完成
}int main() {cin >> n; // 输入待拆分的数字 nNum[1] = 1; // 初始化第一个数字为 1for (int p = 1; p >= 1;) { // p 是堆栈的指针,指向当前拆分的位置// 如果当前数字的和没有超过 nif (sum + Num[p] < n) {sum += Num[p]; // 将当前数字加入到和中p++; // 移动到下一个位置Num[p] = Num[p - 1]; // 后续数字至少等于前一个数字,保证拆分是递增的}else { // 当当前数字的和超过或等于 nif (sum + Num[p] == n && p != 1) // 确保不打印 n = n 的情况Print(p); // 找到一个合法的拆分,输出该拆分p--; // 回退到上一个位置,尝试新的拆分方式sum -= Num[p]; // 从当前和中去掉刚刚回退的数字Num[p]++; // 增加当前数字,继续尝试拆分}}return 0;
}
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;int n, minDifference = INT_MAX; // n 是配料种类数,minDifference 用于记录最小的酸度和苦度差
vector<pair<int, int>> ingredients; // 存储每种配料的酸度和苦度// 递归函数:枚举所有配料组合,计算酸度和苦度的最小绝对差
void findMinDifference(int index, int sour, int bitter, bool used) {if (index == n) { // 终止条件:遍历完所有配料if (used) { // 检查是否至少选择了一种配料// 更新最小绝对差minDifference = min(minDifference, abs(sour - bitter));}return;}// 情况1:选择当前配料// 更新酸度为 sour * 当前配料的酸度,苦度为 bitter + 当前配料的苦度findMinDifference(index + 1, sour * ingredients[index].first, bitter + ingredients[index].second, true);// 情况2:不选择当前配料// 保持酸度和苦度不变,继续递归findMinDifference(index + 1, sour, bitter, used);
}int main() {cin >> n; // 读取配料的种类数ingredients.resize(n); // 调整配料数组大小// 读取每种配料的酸度和苦度for (int i = 0; i < n; i++) {cin >> ingredients[i].first >> ingredients[i].second;}// 从第一个配料开始递归,初始酸度为1,苦度为0,未选择任何配料findMinDifference(0, 1, 0, false);cout << minDifference << endl; // 输出最小绝对差return 0;
}
这是一道递归搜索题,可以通过深度优先搜索(DFS)来求解。具体思路如下:
-
数据结构:
- 使用
vector<string>
存储所有输入的单词。 - 记录每个单词在构造的“龙”中使用的次数,最多允许使用两次。
- 使用
-
递归搜索:
- 从以指定开头字母的单词开始构造“龙”,每次递归时寻找下一个可以连接的单词。
- 判断两个单词是否可以连接:一个单词的末尾部分和另一个单词的开头部分是否匹配。匹配时,允许从第一个单词尾部截取一部分,和第二个单词的开头重合。
-
长度计算:
- 每次递归调用中,将当前“龙”的长度与记录的最长长度进行比较,更新最长长度。
-
递归终止:
- 当遍历完所有可能的连接时,返回到上层递归,继续寻找其他可能的组合。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;int n; // 单词数
vector<string> words; // 存储所有单词
vector<int> wordCount; // 记录每个单词的使用次数,确保每个单词最多使用两次
int maxLength = 0; // 最长“龙”的长度// 检查两个单词能否连接,并返回连接后的长度增量
int canConnect(const string &a, const string &b) {int lenA = a.length();int lenB = b.length();// 从 1 开始遍历,避免完全重合的情况for (int i = 1; i < lenA; i++) {// 检查 a 的后缀和 b 的前缀是否匹配if (a.substr(lenA - i) == b.substr(0, i)) {return lenB - i; // 返回 b 中去掉重合部分后的长度}}return 0; // 无法连接时返回 0
}// 深度优先搜索 (DFS) 函数,用于构造“单词接龙”
void dfs(const string ¤tDragon, int currentLength) {// 更新当前最长的“龙”的长度maxLength = max(maxLength, currentLength);// 遍历所有单词,尝试进行连接for (int i = 0; i < n; i++) {// 确保单词使用次数不超过两次if (wordCount[i] < 2) {// 检查 currentDragon 和 words[i] 是否可以连接int lengthIncrease = canConnect(currentDragon, words[i]);if (lengthIncrease > 0) { // 如果可以连接wordCount[i]++; // 增加使用次数// 递归构造新的接龙,增加接龙长度dfs(currentDragon + words[i].substr(words[i].length() - lengthIncrease), currentLength + lengthIncrease);wordCount[i]--; // 回溯,恢复该单词的使用次数}}}
}int main() {cin >> n;words.resize(n);wordCount.resize(n, 0); // 初始化每个单词的使用次数为 0for (int i = 0; i < n; i++) {cin >> words[i];}char startChar;cin >> startChar;// 寻找以指定字母开头的单词作为起点for (int i = 0; i < n; i++) {if (words[i][0] == startChar) { // 找到以 startChar 开头的单词wordCount[i]++; // 使用该单词// 从该单词开始构造“龙”,并进行深度优先搜索dfs(words[i], words[i].length());wordCount[i]--; // 回溯}}// 输出最长“龙”的长度cout << maxLength << endl;return 0;
}
这个问题是典型的 N 皇后问题的变体,要求在 n x n
的棋盘上放置 n
个棋子(称作皇后),使得每行、每列和每条对角线至多有一个棋子。我们可以通过递归和回溯来实现解法。
以下是对解决方法的详细解释:
思路
-
皇后位置的表示:
- 用一个长度为
n
的数组solution
来表示解,其中solution[i]
表示第i
行的皇后放置在哪一列。
- 用一个长度为
-
约束条件:
- 每一行只能有一个皇后。
- 每一列只能有一个皇后。
- 每条对角线上只能有一个皇后。
-
回溯法:
- 从第一行开始尝试在每一列放置皇后,逐步填充每一行,检查是否符合条件。
- 如果某一列放置后,满足不在同一列、不在同一对角线,就继续放置下一行的皇后。
- 如果不满足条件,则回溯,尝试下一列。
-
对角线的判断:
- 对角线的冲突可以通过两个辅助数组
diag1
和diag2
来检测。diag1[i + j]
:表示从左上到右下的对角线,i + j
是该对角线的唯一标识。diag2[i - j + n - 1]
:表示从右上到左下的对角线,i - j + n - 1
是该对角线的唯一标识。
- 对角线的冲突可以通过两个辅助数组
-
按字典序排列:
- 按列号递增顺序逐个放置皇后,这样天然符合字典序。
-
输出限制:
- 只需要输出前三个解,之后只需计数。
#include <iostream>
#include <vector>
using namespace std;int n; // 棋盘大小,即 n x n 大小的棋盘
vector<int> solution; // 当前解,solution[i] 表示第 i 行皇后所在的列
vector<bool> column; // 标记某一列是否已有皇后
vector<bool> diag1, diag2; // 标记两条对角线是否已有皇后
int solutionCount = 0; // 解的总数// 输出一个完整解,将每行的列号打印
void printSolution() {for (int i = 0; i < n; i++) {cout << solution[i] + 1; // 将列号转换为从 1 开始if (i < n - 1) cout << " ";}cout << endl;
}// 递归搜索函数 row 表示当前行
void solve(int row) {// 如果 row 等于 n,说明已成功放置 n 个皇后,找到一个解if (row == n) {solutionCount++; // 解的计数加一if (solutionCount <= 3) {printSolution(); // 前三个解按题目要求输出}return;}// 尝试在当前行的每一列放置皇后for (int col = 0; col < n; col++) {// 检查当前列和两个对角线是否可以放置皇后if (!column[col] && !diag1[row + col] && !diag2[row - col + n - 1]) {solution[row] = col; // 放置皇后在第 row 行的第 col 列column[col] = diag1[row + col] = diag2[row - col + n - 1] = true;solve(row + 1); // 递归处理下一行// 回溯:移除皇后并恢复标记column[col] = diag1[row + col] = diag2[row - col + n - 1] = false;}}
}int main() {cin >> n;solution.resize(n); // 初始化解数组大小column.assign(n, false); // 初始化列标记数组为 false,表示未被占用diag1.assign(2 * n - 1, false); // 初始化对角线数组为 false,表示未被占用diag2.assign(2 * n - 1, false); // 初始化反对角线数组为 falsesolve(0); // 从第 0 行开始递归搜索cout << solutionCount << endl; // 输出解的总数return 0;
}
在 N 皇后问题中,我们需要确保每个皇后不仅不在同一行和同一列上,还要确保它们不在同一条对角线上。为了高效地检查对角线的冲突,diag1
和 diag2
这两个数组被用来标记棋盘上的对角线是否已经有皇后。
具体来说,diag1
和 diag2
用来分别表示两种类型的对角线:
1. diag1
(左上到右下的对角线)
-
表示方向:从左上到右下的对角线(\ 方向)。
-
数组定义:
diag1[row + col]
- 解释:
diag1
数组的索引是row + col
,它代表了左上到右下的每一条对角线。对于一个给定的棋盘位置(row, col)
,它所在的对角线的索引为row + col
。 - 原因:所有位于同一条左上到右下对角线的格子的
row + col
值是相同的。例如,位置(0, 0)
、(1, 1)
、(2, 2)
都在同一条对角线上,它们的row + col
都是 0、2、4 等等。
- 解释:
-
例子:
- 对于
(0, 0)
,diag1[0]
为true
表示左上到右下对角线上已经有皇后。 - 对于
(1, 1)
,diag1[2]
为true
表示同一条对角线上已经有皇后。
- 对于
-
初始化:在开始时,
diag1
数组的所有元素都为false
,表示没有皇后占据任何对角线。