Codeforces 765F 【Souvenirs】
Description
Solution
首先容易想到要把询问离线掉,按照套路枚举右端点\(r\),\(ans_i\)表示\([i, r]\)的答案。
这样我们只需要考虑每次加进来一个点\(a_r\)对所有答案的贡献。
以\(a_r\)将点分为两种:权值大于它的和小于它的。
因为两种是对称的所以只讨论权值大于它的。
先找到最靠右的大于它的点\(a_x\),这个点可能不是任何区间的最优答案,但是它能更新到的区间是最多的,即左端点为\(1 \dots x\),右端点为\(r\)的所有区间。
这样再在\(x\)左边找可以更新答案的点\(a_z\),它能更新到的区间是左端点为\(1 \dots z\),右端点为\(r\)的所有区间,也就是说它能更新到的区间是被\(a_x\)能更新的区间完全包含的。所以如果新的点的权值比\(a_x\)还大,肯定不会更优,也就是说\(a_z\)必须小于\(a_x\)。
但是这样还是不行,严格递增序列会把这样的过程卡到单次\(O(n)\)。
考虑之前以右端点枚举到\(x\)的时候,\(a_z\)能更新的所有区间已经被\(a_x - a_z\)更新过了,那么如果现在的\(a_z - a_r\)比\(a_x - a_z\)更大,就没有任何用处了。
化简这个式子,有
\[a_z < \frac{a_x + a_r}{2}
\]
这样枚举右端点之后每次只需要找\(log(n)\)次就行了,每次查找的复杂度是\(o(logn)\)的,那么总复杂度是\(O(n log^2 n)\)。
每次要用某个权值去更新一段前缀的答案,直接吉司机线段树显然非常不行。
所以考虑只对某个点更新,查询的时候查一个后缀最小值,用树状数组实现即可。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 100000;
const int INF = 0x7f7f7f7f;
int n, a[N + 50], root[N + 50], tot, cwq, b[N + 50], q, pos;
struct Ask
{
int l, r, id, ans;
} ask[N * 3 + 50];
struct Bit
{
int minn[N + 50];
void Pre()
{
memset(minn, 0x7f, sizeof(minn));
return;
}
int Lowbit(int x)
{
return x & -x;
}
void Update(int pos, int v)
{
for (int i = pos; i <= n; i += Lowbit(i))
minn[i] = min(minn[i], v);
return;
}
int Query(int pos)
{
int minnn = INF;
for (int i = pos; i; i -= Lowbit(i)) minnn = min(minnn, minn[i]);
return minnn;
}
} bit;
struct SegmentTree
{
int s[(N << 2) + 50];
void Update(int k, int l, int r, int pos, int zhi)
{
if (l == r)
{
s[k] = zhi;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) Update(k << 1, l, mid, pos, zhi);
else Update(k << 1 | 1, mid + 1, r, pos, zhi);
s[k] = max(s[k << 1], s[k << 1 | 1]);
return;
}
int Query(int k, int l, int r, int x, int y)
{
if (x > y) return 0;
if (x <= l && r <= y) return s[k];
int mid = (l + r) >> 1;
if (y <= mid) return Query(k << 1, l, mid, x, y);
else if (x > mid) return Query(k << 1 | 1, mid + 1, r, x, y);
else return max(Query(k << 1, l, mid, x, y), Query(k << 1 | 1, mid + 1, r, x, y));
}
} tr;
void Read(int &x)
{
x = 0; int p = 0; char st = getchar();
while (st < '0' || st > '9') p = (st == '-'), st = getchar();
while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
x = p ? -x : x;
return;
}
void Print(int x)
{
if (x > 9) Print(x / 10);
putchar(x % 10 + '0');
return;
}
int Cmp(Ask a, Ask b)
{
return a.r < b.r;
}
int Cmp1(Ask a, Ask b)
{
return a.id < b.id;
}
int Abs(int x)
{
return x < 0 ? -x : x;
}
int Find(int x)
{
int l = 0, r = cwq;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (b[mid] <= x) l = mid;
else r = mid - 1;
}
return l;
}
int main()
{
Read(n);
for (int i = 1; i <= n; i++) Read(a[i]), b[++cwq] = a[i];
sort(b + 1, b + cwq + 1);
cwq = unique(b + 1, b + cwq + 1) - b - 1;
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + cwq + 1, a[i]) - b;
Read(q);
for (int i = 1; i <= q; i++) Read(ask[i].l), Read(ask[i].r), ask[i].id = i;
sort(ask + 1, ask + q + 1, Cmp);
pos = 1;
bit.Pre();
for (int i = 1; i <= n; i++)
{
if (i != 1)
{
int sj = cwq, xj = a[i], tmp;
tmp = tr.Query(1, 1, cwq, xj, sj);
while (tmp)
{
bit.Update(n - tmp + 1, Abs(b[a[tmp]] - b[a[i]]));
sj = Find((b[a[tmp]] + b[a[i]]) / 2);
if (!sj) break;
if (sj >= a[tmp]) sj = a[tmp] - 1;
tmp = tr.Query(1, 1, cwq, xj, sj);
}
sj = a[i], xj = 1;
tmp = tr.Query(1, 1, cwq, xj, sj);
while (tmp)
{
bit.Update(n - tmp + 1, Abs(b[a[tmp]] - b[a[i]]));
xj = Find((b[a[tmp]] + b[a[i]]) / 2);
if (!xj) break;
if (xj <= a[tmp]) xj = a[tmp] + 1;
tmp = tr.Query(1, 1, cwq, xj, sj);
}
while (ask[pos].r == i)
{
ask[pos].ans = bit.Query(n - ask[pos].l + 1);
pos++;
}
}
tr.Update(1, 1, cwq, a[i], i);
}
sort(ask + 1, ask + q + 1, Cmp1);
for (int i = 1; i <= q; i++) Print(ask[i].ans), putchar('\n');
return 0;
}