题解:P14638 [NOIP2025] 序列询问 / query(民间数据)
解法一:几何法
令
那么原题可以转化为:对于所有的点
这个梯形可以划分成一个平行四边形和一个等腰直角三角形。
取等腰直角三角形的三边中点,可以用 2 个平行四边形和一个正方形将其覆盖。
平行四边形
以
考虑其中一条线段
所以
这是一个非常典型的滑动窗口,可以通过单调队列求解;其中
竖着的
正方形
设在
特殊情况
特别需要注意的是,当
实现
写代码的时候最好把图放边上,不然会被这一堆的坐标绕晕,同时还要注意边界条件。
显然总时间复杂度为 std::deque 会 TLE!!!
::::success[Code]
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MAXN = 5e4 + 5;
struct MonoQueue {
int idx[MAXN];
ll val[MAXN];
int h, t;
inline void reset() {
h = 1;
t = 0;
}
inline void push(ll v, int pos) {
while (h <= t && val[t] <= v)
t--;
t++;
idx[t] = pos;
val[t] = v;
}
inline void pop(int limit) {
while (h <= t && idx[h] < limit)
h++;
}
inline ll top() { return val[h]; }
inline bool empty() { return h > t; }
} q1, q2, q3;
int main() {
#ifndef ONLINE_JUDGE
cin.tie(0)->sync_with_stdio(0);
#endif
int n;
cin >> n;
int m = __lg(n) + 1;
vector s(n + 1, 0ll);
vector f(m, vector(n + 1, INT64_MIN)), g(m, vector(n + 1, INT64_MAX));
f[0][0] = g[0][0] = 0;
for (int i = 1; i <= n; i++) {
cin >> s[i];
s[i] += s[i - 1];
f[0][i] = g[0][i] = s[i];
}
for (int j = 1; j <= __lg(n); ++j) {
int l = 1 << (j - 1);
for (int i = 0; i + (1 << j) - 1 <= n; ++i) {
f[j][i] = max(f[j - 1][i], f[j - 1][i + l]);
g[j][i] = min(g[j - 1][i], g[j - 1][i + l]);
}
}
auto qmax = [&](int l, int r) {
int j = __lg(r - l + 1);
return max(f[j][l], f[j][r - (1 << j) + 1]);
};
auto qmin = [&](int l, int r) {
int j = __lg(r - l + 1);
return min(g[j][l], g[j][r - (1 << j) + 1]);
};
int q;
cin >> q;
for (int l, r; q--;) {
cin >> l >> r;
uint64_t ans = 0;
q1.reset(), q2.reset(), q3.reset();
for (int i = 1; i <= n; ++i) {
ll res = INT64_MIN;
if (i + l - 1 <= n) {
ll v1 = s[i + l - 1] - qmin(max(1, i - r + l) - 1, i - 1);
q1.push(v1, i + l - 1);
}
if (i + (r + l) / 2 - 1 <= n) {
ll v2 = s[i + (r + l) / 2 - 1] - qmin(max(1, i - (r - l + 1) / 2) - 1, i - 1);
q2.push(v2, i + (r + l) / 2 - 1);
ll v3 = qmax(i + (r + l) / 2 - 1, min(n, i + r - 1)) - s[i - 1];
q3.push(v3, i);
}
q1.pop(i);
q2.pop(i + l - 1);
q3.pop(i - (r - l) / 2);
if (!q1.empty()) res = max(res, q1.top());
if (!q2.empty()) res = max(res, q2.top());
if (!q3.empty()) res = max(res, q3.top());
if (i + l - 1 <= n) {
ll v4 = qmax(i + l - 1, min(n, i + (l + r) / 2 - 1)) -
qmin(max(1, i - (r - l) / 2) - 1, i - 1);
res = max(res, v4);
}
ans ^= uint64_t(res) * i;
}
cout << ans << '\n';
}
return 0;
}
::::
解法二
假设当前已经有了所有左端点小于等于
如果
S 中的极好区间A 的右端点比极好区间B 小且A 的区间和小于等于B 的区间和,显然A 是不优的,因为B 的和更大且能覆盖更多位置。
对于
- 从
S 中移除右端点小于i 的区间; - 加入以
i 为左端点的最优极好区间; - 移除不再最优的区间;
- 从
S 中取出最大区间和作为i 的答案。
这些步骤完美契合了滑动窗口的求解过程,可以使用单调队列维护
考虑如何计算以
-
r\le n -
i+L-1\le r\le i+R-1 - 不存在
r-R+1\le k<i 满足s_r-s_{k-1}\ge s_r-s_{i-1} ,即r 不能有更优的左端点与之匹配 - 上述条件会将
r 的取值限制在一个区间[r_1,r_2] 中,r 还需要满足r\ge r_{\rm best}=\argmax_{r_1\le j\le r_2}(s_j-s_{i-1})=\argmax_{r_1\le j\le r_2}s_j ;特别的,如果区间[r_1,r_2] 非法,则r_{\rm best}=\infty - 不存在
r'>r 满足s_{r'}-s_{i-1}\ge s_r-s_{i-1} (这一步可以不管,单调队列会自动删除这些r )
第 3 点中的
故第 4 点中的
但是
进一步观察可以发现,当
考虑对于
j>i 的位置:如果
{\rm leftLess}_{i-1}\le{\rm leftLess}_{j-1} ,那么{\rm Left}_j 的 RMQ 区间是比{\rm Left}_j 更靠右的,于是有{\rm Left}_i\le {\rm Left}_j ,那么r>{\rm Left}_j\ge{\rm Left}_i 显然不会是i 的答案;如果
{\rm leftLess}_{i-1}>{\rm leftLess}_{j-1} 且{\rm Left}_i\le {\rm Left}_j ,同上;如果
{\rm leftLess}_{i-1}>{\rm leftLess}_{j-1} 且{\rm Left}_i>{\rm Left}_j ,此时{\rm Left}_j 的 RMQ 区间是包含{\rm Left}_i 的 RMQ 区间的,那么有s_{{\rm Left}_j}>s_{{\rm Left}_i} ,又因为i+L-1<j+L-1\le {\rm Left}_j<{\rm Left}_i\le i+R-1 ,所以[i, {\rm Left}_i] 比[i, {\rm Left}_j] 更劣,所以当r\ge {\rm Left}_i>{\rm Left}_j 时,同样不会作为i 的答案。\square
所以可以进一步限制
暴力将区间
实现
时间复杂度
::::success[Code]
#include <bits/stdc++.h>
using namespace std;
using ll = int64_t;
int main() {
#ifndef ONLINE_JUDGE
cin.tie(0)->sync_with_stdio(0);
#endif
int n;
cin >> n;
int m = __lg(n) + 1;
vector s(n + 1, 0ll);
vector f(m, vector(n + 1, 0));
for (int i = 1; i <= n; i++) {
cin >> s[i];
s[i] += s[i - 1];
f[0][i] = i;
}
auto argmax = [&](int x, int y) {
return (s[x] == s[y] ? x > y : s[x] > s[y]) ? x : y;
};
for (int j = 1; j <= __lg(n); ++j) {
int l = 1 << (j - 1);
for (int i = 0; i + (1 << j) - 1 <= n; ++i) {
f[j][i] = argmax(f[j - 1][i], f[j - 1][i + l]);
}
}
auto qmax = [&](int l, int r) {
if (l > r)
return n + 1;
int j = __lg(r - l + 1);
return argmax(f[j][l], f[j][r - (1 << j) + 1]);
};
stack<int> stk;
vector<int> leftLess(n + 1), L(n + 1), R(n + 1);
for (int i = 0; i <= n; ++i) {
while (!stk.empty() && s[stk.top()] > s[i])
stk.pop();
leftLess[i] = stk.empty() ? INT_MIN : stk.top();
stk.push(i);
}
int q;
cin >> q;
for (int l, r; q--;) {
cin >> l >> r;
uint64_t ans = 0;
for (int i = n, minL = n; i; --i) {
R[i] = min(minL, i + r - 1);
L[i] = qmax(max(leftLess[i - 1] + 1 + r, i + l - 1), min(n, i + r - 1));
minL = min(minL, L[i]);
}
deque<pair<int64_t, int>> Q;
for (int i = 1; i <= n; ++i) {
while (!Q.empty() && Q.front().second < i)
Q.pop_front();
for (int j = L[i]; j <= R[i]; ++j) {
auto val = s[j] - s[i - 1];
while (!Q.empty() && val > Q.back().first)
Q.pop_back();
Q.emplace_back(val, j);
}
ans ^= uint64_t(Q.front().first) * i;
}
cout << ans << '\n';
}
return 0;
}
::::
冷静分析可以发现,这题本质上其实并不难,可能是大家在考场上被吓到了吧,估计过段时间就会回归到紫题,先