「CH0805」防线
「CH0805」防线
题意
给出一个数列, 里面至多有 1 个奇数, 其他都是偶数,求这个奇数的大小和位置。
解
是不是看起来很简单?但是这里数列不是直接给出的,会给出 \(n\) 个类型防具, 每个防具有三个参数 \(s, e, d\),分别表示从 \(s\) 到 \(e\) 每隔 \(d\) 有一个防具。
最后的数列就是每个点防具之和。
可能你也猜到了, \(e\) 的大小是小于 \(2_{31}-1\), \(n\le10^8\)
直接用数组模拟的计划没了,即使你开的数组可能包含答案, 但是出题人一定会卡你的,也不必尝试了。
思考一下题目的元素:
- 一个奇数和若干个偶数
- 数据范围莫名的大
可以想到二分答案来枚举。
check 的写法
可以想到,如果 \(1, 2, 3, \dots , mid - 1, mid\) 里面有奇数就往这个范围搜, 没有就往 \(mid + 1, mid + 2, mid + 3 \dots , n - 1, n\) 的范围搜。
问题是如何判断一个范围里面是否有奇数?这里保证的还是只有一个奇数。
是不是直接一个前缀和判断一下奇偶就可以了?
新的问题有接踵而至,怎么求前缀和,这奇怪的输入方式应该如何处理?
看下图:

可以发现这里面有 \((e -s) / d\) 个区间,根据小学知识就可以知道里面有 \((e-s)/ d+1\) 条线段,即\((e-s)/ d+1\)个防具。
现在要求 \(1\) 到 \(x\) 的前缀和,到最后一个防具只有下面的两种情况
- x 被包含在这个区间里面
对于这种情况,求 \((x-s)/d+1\) 就可以了,不包含不会求到最后一个 \(d\)。
- x没有被包含在区间里面
不看第一个区间,可以发现 \(x\) 被包含到了第二个区间,问题转换到了第一种情况。
于是只用处理第一种情况就可以了。即取 std::min(s, x)
就好了
求答案
第一个答案二分就可以了,在下文中记录为 \(ans\)。
第二个答案是求大小,回忆前缀和如何求区间答案, 是不是 sum[r] - sum[l - 1]
?
在这种情况下 \(l=r\), 答案就是 sum[ans] - sum[ans - 1]
。
Code
#include <bits/stdc++.h>
using i64 = long long;
inline i64 read(){
i64 x = 0, f = 1;char ch = getchar();
while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return x * f;
}
constexpr i64 INF = i64_MAX;
i64 main() {
struct node {
i64 l, r, d;
};
auto solve = [&] () {
i64 n = read();
std::vector<node> a(n + 1);
i64 l = 0, r = -1;
for (i64 i = 1; i <= n; i++) {
a[i].l = read(), a[i].r = read(), a[i].d = read();
r = std::max(r, a[i].r);
}
// 计算前缀和
std::function<i64(i64)> sum = [&](i64 x) {
i64 ans = 0;
for (i64 i = 1; i <= n; i++) {
if (a[i].l <= x) {
ans += (std::min(a[i].r, x) - a[i].l) / a[i].d + 1;
}
}
return ans;
};
i64 ans = -1;
while (l <= r) {
i64 mid = l + r >> 1;
if (sum(mid) & 1) ans = mid, r = mid - 1;
else l = mid + 1;
}
i64 ans2 = sum(ans) - sum(ans - 1);
ans == -1 ? std::cout << "There's no weakness.\n" : std::cout << ans << " " << ans2 << "\n";
};
i64 T = read();
while (T--)
solve();
return 0;
}