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;
}
posted @ 2020-10-15 20:54  Tian-Xing  阅读(130)  评论(0编辑  收藏  举报