旅行青蛙
旅行是一件非常令呱愉快的事情, 但即使是经验丰富的旅行蛙们也不得不经常面临一个现实问题: 路途一小时, 玩耍五分钟.
为了有效地解决这个问题, Efve 决定将旅行可能用到的交通线路全部用电脑记下来, 他希望能找出一条旅行线路, 使自己的呱尽可能愉悦地享受旅程.
但是, Efve 并不认为花费时间最少的线路就一定最能使自己的呱愉悦. 比起在 frog mountain frog sea 的地铁上挤半小时还得换站三次, 他宁愿选择让呱在空旷的公交上睡五十分钟直达目的地. 因此, Efve 为交通线路定义了一个愉悦值, 接着结合各种数据, 算出了每条交通线路的愉悦值, 其中不乏一些完美线路, 不会对呱的心情产生任何影响. 然而, 由于呱呱的心情飘忽不定 (可能便当不合胃口), Efve 每次旅行前也会给他的呱算出一个初始愉悦值. 在到达目的地之前, 呱呱每经过一条愉悦值低于其当前愉悦值的线路, 它的愉悦值就会降低到与该线路愉悦值相同.
现在给出 Efve 记录的所有交通线路 (包括起点、终点、愉悦值和花费时间) , 以及他的呱某次旅行的出发点、目的地和旅行前的初始愉悦值. Efve 希望你能告诉他:
- 到达目的地时, 他的呱愉悦值最大是多少?
- 在保证愉悦值最大的前提下, 路上花费的总时间最少是多少?
输入描述
有若干组数据, 对于每组数据:
- 第一行, 两个整数 N 和 M, 表示 Efve 记录的 M 条线路中, 共有 N 个不同的地点, 标号为 1 到 N.
- 以下 M 行, 每行四个整数 xi, yi, hi 和 ti, 表示在地点 xi 和 yi 之间有一条愉悦值为 hi, 花费时间为 ti 的双向交通线路. 特殊的, 当 hi 的值为 -1 时, 表示该线路是一条完美线路.
- 最后一行, 三个正整数 S, T 和 H, 表示 Efve 的呱打算从地点 S 出发前往地点 T, 而它的初始愉悦值为 H.
最后一组数据后一行有两个整数 "0 0", 表示输入结束.
输出描述
对于每组数据, 单独输出一行. 如果 Efve 的呱完成不了它的旅行计划, 输出 "Impossible!". 否则输出两个整数, 分别表示 Efve 的呱从地点 S 到达地点 T 时的最大愉悦值与愉悦值最大的前提下花费的最少总时间. 对于输入的最后一行无需给出输出.
补充说明
对于 10% 的数据: N ≤ 20, M ≤ 200.
对于 30% 的数据: N ≤ 200, M ≤ 2000.
对于 60% 的数据: N ≤ 2000, M ≤ 20000.
对于 100% 的数据: 2 ≤ N ≤ 20000, 0 ≤ M ≤ 200000, 1 ≤ xi, yi, S, T ≤ N, xi ≠ yi, S ≠ T, 1 ≤ hi ≤ 10^9 或 hi = -1, 1 ≤ H ≤ 10^9, 1 ≤ ti ≤ 10^5.
每个输入包括的数据组数不超过四组.
示例
输入
5 6
1 2 7 5
1 3 4 2
2 4 -1 10
2 5 2 4
3 4 10 1
4 5 8 5
1 5 10
3 1
1 2 -1 100
1 3 10
5 6
1 2 7 5
1 3 4 2
2 4 -1 10
2 5 2 4
3 4 10 1
4 5 8 5
1 5 4
20 0
1 20 233
0 0
输出
7 20
Impossible!
4 8
Impossible!
样例包含四组数据, 其最优路线分别为:
- 1 => 2 => 4 => 5
- 无解
- 1 => 3 => 4 => 5
- 无解
标准解法
二分答案+建新图+迪杰斯特拉
C++
#include <vector>
#include <queue>
#include <iostream>
#define INF (std::numeric_limits<int64_t>::max())
struct Edge {
int other;
int64_t h; // 愉悦值。当 hi 的值为 -1 时, 表示该线路是一条完美线路.
int64_t t; // 花费时间
};
using Edges = std::vector<Edge>;
int n; // 地点数
int m; // 线路数
int start, target;
std::vector<Edges> graph;
std::vector<Edges> new_graph;
using DistPair = std::pair<int64_t, int>;
std::vector<int64_t> dist;
void Dikstra() {
dist.assign(n, INF);
dist[start] = 0;
std::priority_queue<DistPair, std::vector<DistPair>, std::greater<DistPair>> pq;
pq.push({ 0, start });
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (const Edge& edge : new_graph[u]) {
int v = edge.other;
int64_t t = edge.t;
if (dist[u] + t < dist[v]) {
dist[v] = dist[u] + t;
pq.push({ dist[v], v });
}
}
}
}
int64_t Check(int64_t x) {
// 建新图
// 如果原图中该边的愉悦值比x低,则该边在新图中不存在
new_graph.assign(n, Edges());
for (int node = 0; node < n; ++node) {
for (const Edge& edge : graph[node]) {
if (edge.h >= x || edge.h == -1) {
new_graph[node].push_back({ edge.other, edge.h, edge.t });
}
}
}
Dikstra();
return dist[target];
}
int main() {
while (std::cin >> n >> m) {
if (!n && !m) break;
/*
以下 M 行, 每行四个整数 xi, yi, hi 和 ti,
表示在地点 xi 和 yi 之间有一条愉悦值为 hi,
花费时间为 ti 的双向交通线路.
特殊的, 当 hi 的值为 -1 时, 表示该线路是一条完美线路.
*/
graph.assign(n, Edges());
for (int i = 0; i < m; ++i) {
int x, y;
int64_t h, t;
std::cin >> x >> y >> h >> t;
x -= 1;
y -= 1;
graph[x].push_back({ y, h, t });
graph[y].push_back({ x, h, t });
}
// 最后一行, 三个正整数 S, T 和 H, 表示 Efve 的呱打算从地点 S 出发前往地点 T, 而它的初始愉悦值为 H.
int64_t initial_h;
std::cin >> start >> target >> initial_h;
start -= 1;
target -= 1;
/*
如果 Efve 的呱完成不了它的旅行计划, 输出 "Impossible!".
否则输出两个整数, 分别表示 Efve 的呱从地点 S 到达地点 T 时的最大愉悦值
与愉悦值最大的前提下花费的最少总时间.
*/
int64_t i = 0;
int64_t j = initial_h + 1;
// [0, i) [i, j) [j, n]
// 要求可行 unknown 要求不可行
int64_t shortest_path = 0;
bool can = false;
while (i < j) {
int64_t mid = (i + j) / 2;
int64_t curr_path = Check(mid);
if (curr_path < INF) {
i = mid + 1;
shortest_path = curr_path;
can = true;
}
else {
j = mid;
}
}
if (!can) {
std::cout << "Impossible!" << std::endl;
continue;
}
std::cout << i - 1 << " " << shortest_path << std::endl;
}
}
最长连续子串
灵小犀在研究古代文献时发现了一个神秘的数字序列问题。给定一个长度为n的整数序列,灵小犀需要找到一个最长的连续子串,使得这个子串中的所有元素都满足以下条件:
- 子串中所有元素的和为偶数
- 子串中任意两个相邻元素的差的绝对值不超过k
你的任务是帮助灵小犀找到满足条件的最长连续子串的长度。
输入描述
第一行包含两个整数n和k,分别表示序列长度和差值限制。
第二行包含n个整数,表示给定的整数序列。
输出描述
输出一个整数,表示满足条件的最长连续子串的长度。如果不存在满足条件的子串,输出0。
补充说明
- 1 ≤ n ≤ 10^5
- 0 ≤ k ≤ 10^9
- 序列中元素的绝对值不超过10^9
示例
输入
7 2
1 3 2 4 6 5 7
输出
7
说明:
针对序列[1, 3, 2, 4, 6, 5, 7],可能的子串有:
-
1, 3\] - 长度2,和为4(偶数),相邻差值\|3-1\|=2≤2
-
2, 4\] - 长度2,和为6(偶数),相邻差值\|4-2\|=2≤2
-
6, 5\] - 长度2,和为11(奇数),不满足条件
-
2, 4, 6\] - 长度3,和为12(偶数),相邻差值\|4-2\|=2≤2, \|6-4\|=2≤2
-
3, 2, 4, 6, 5\] - 长度5,和为20(偶数),检查相邻差值: \|2-3\|=1≤2, \|4-2\|=2≤2, \|6-4\|=2≤2, \|5-6\|=1≤2
因此,最长满足条件的子串长度为7。
暴力解法
双重循环+剪枝,能过
C++
#include <bits/stdc++.h>
#include <ios>
bool Check(const std::vector<int64_t>& nums, int64_t k, int i, int j) {
int64_t sum = nums[i];
for (i = i + 1; i <= j; ++i) {
sum += nums[i];
if (std::abs(nums[i] - nums[i - 1]) > k) {
return false;
}
}
return sum % 2 == 0;
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n;
int64_t k;
std::cin >> n >> k;
std::vector<int64_t> nums(n);
for (int i = 0; i < n; ++i) {
std::cin >> nums[i];
}
// 最长连续子串
int ans = 0;
// j - i + 1 > ans
// j > ans + i - 1
for (int i = 0; i < n; ++i) {
for (int j = i + ans; j < n; ++j) {
if (Check(nums, k, i, j)) {
ans = std::max(ans, j - i + 1);
}
}
}
std::cout << ans << std::endl;
}
优化解法
核心思路:
- 分块处理:整个序列可以根据"相邻元素差的绝对值不超过k"这个条件,被自然地分割成若干个独立的、满足该条件的连续块。最终的答案必然是某个块内的最长子串,而不可能跨越两个块。
- 前缀和奇偶性:利用前缀和的奇偶性来在线性时间内解决"寻找和为偶数的最长子串"这个问题。其核心思想是:子串 [i, j] 的和为偶数,等价于 prefix_sum[j] 和 prefix_sum[i-1] 的奇偶性相同。通过记录每种奇偶性(偶数0,奇数1)的前缀和第一次出现的位置,可以快速计算出以当前位置 p 为结尾的最长偶数和子串的长度。
这种解法的时间复杂度为O(n)。
C++
/*
关键结论:
假设区间[i, j]中元素之和是偶数,即sum(i, j) % 2 == 0.
那么有[sum(0, j) - sum(0, i - 1)] % 2 == 0,
进一步可以导出sum(0, j) % 2 == sum(0, i - 1) % 2.
也就是说,在i-1固定的情况下,j越大,得到的和为偶数的子串越长。
*/
#include <iostream>
#include <vector>
#include <array>
int Solve(const std::vector<int64_t>& nums, int i, int j) {
int64_t sum = 0;
// pos[0] 记录偶数前缀和首次出现的位置
// pos[1] 记录奇数前缀和首次出现的位置
// i-1 代表子串开始前的位置,此时前缀和为0(偶数)
// i-2 是一个哨兵值,表示该种奇偶性的前缀和还未出现过
std::array<int, 2> pos = {i - 1, i - 2};
int ans = 0;
for (int p = i; p <= j; ++p) {
sum += nums[p];
// `(sum % 2 + 2) % 2`是负数安全版本的`sum % 2`
int cur_parity = (sum % 2 + 2) % 2;
if (pos[cur_parity] > i - 2) {
// 如果这个奇偶性已经出现过,直接更新答案即可
ans = std::max(ans, p - pos[cur_parity]);
} else {
// 如果这个奇偶性是第一次出现,记录其位置
pos[cur_parity] = p;
}
}
return ans;
}
int main() {
int n, k;
std::cin >> n >> k;
std::vector<int64_t> nums(n);
for (int i = 0; i < n; ++i) {
std::cin >> nums[i];
}
int i = 0;
int j = 0;
int ans = 0;
while (j < n) {
// 利用题目中对相邻元素差的约束,对输入字符串进行分片
while (j == 0 || j < n && std::abs(nums[j] - nums[j - 1]) <= k) {
++j;
}
// 求解当前分片中最长的和为偶数的连续子串
ans = std::max(ans, Solve(nums, i, j - 1));
i = j;
}
std::cout << ans << std::endl;
}