CF1824D LuoTianyi and the Function【线段树】

给定长为 \(n\) 的数组 \(a\),如下定义 \(g(i,j)\):当 \(i \leq j\) 时,\(g(i,j)\) 是满足 \(\{ a_p : i \leq p \leq j \} \subseteq \{a_q : x \leq q \leq j\}\) 的最大整数 \(x\)。否则 \(g(i,j) = 0\)

\(q\) 次询问,每次给定 \(l,r,x,y\),求 \(\sum\limits_{i=l}^r \sum\limits_{j=x}^y g(i,j)\)

\(n,q \leq 10^6\),时限 \(\text{7.0s}\)


询问的形式明示我们在二维平面上考虑。考虑按 \(j\) 扫描线,把询问差分成前缀和,那么每次需要查询 \(l \sim r\)\(1\sim j\) 的所有版本的和,如果能够快速维护 \(g(i,j)\),那么套一个历史和就行了。我们考虑 \(g(i,j)\) 是怎么计算的,发现它只和每种颜色最后一次出现的位置有关,固定了 \(j\) 之后,这些位置会把整个序列划分成若干段,每一段的 \(g\) 恰好是右边第一个对应的位置。

于是维护是容易的:我们可以简单地对每个 \(j\) 维护出关键位置的集合,具体来说,加入 \(a_j\) 后可能会把之前的一个位置删掉,设删掉的位置是 \(k\),其在关键位置集合中的前驱是 \(p\),后继是 \(q\),那么对于 \(i \in [p+1,k]\)\(g(i,j)\) 会变成 \(q\)。进一步发现这个区间内原来的 \(g\) 值全都为 \(k\),维护区间加区间历史和即可,时间复杂度 \(\mathcal{O}(n \log n)\)

但是这个破题居然卡常,所以下面这个代码暂时是过不了的。出题人真下头。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 1e6 + 5;
int n, q, a[N], le[N]; LL ans[N];
map <int, int> f;
struct que { int id, l, r; };
vector <que> v[N];
#define m ((l + r) >> 1)
struct dat { LL sum, hsum, len; } tr[N << 2];
struct laz { LL add, hadd, t; } g[N << 2];
inline dat operator + (const dat &x, const dat &y) {
	return (dat){x.sum + y.sum, x.hsum + y.hsum, x.len + y.len};
}
inline dat operator + (const dat &x, const laz &y) {
	dat z;
	z.hsum = x.hsum + x.sum * y.t + y.hadd * x.len;
	z.sum = x.sum + y.add * x.len;
	z.len = x.len;
	return z;
}
inline laz operator + (const laz &x, const laz &y) {
	laz z;
	z.hadd = x.hadd + x.add * y.t + y.hadd;
	z.t = x.t + y.t;
	z.add = x.add + y.add;
	return z;
}
void ptag(int x, laz v) { tr[x] = tr[x] + v, g[x] = g[x] + v; }
void down(int x) {
	if (g[x].t) ptag(x << 1, g[x]), ptag(x << 1 | 1, g[x]), g[x] = {0, 0, 0}; 
}
void build(int x, int l, int r) {
	tr[x].len = r - l + 1;
	if (l == r) return; 
	build(x << 1, l, m), build(x << 1 | 1, m + 1, r);
}
laz cur;
void cov(int x, int l, int r, int ql, int qr) {
	if (ql <= l && qr >= r) return ptag(x, cur);
	down(x);
	if (ql <= m) cov(x << 1, l, m, ql, qr);
	if (qr > m) cov(x << 1 | 1, m + 1, r, ql, qr);
	tr[x] = tr[x << 1] + tr[x << 1 | 1];
}
LL qry(int x, int l, int r, int ql, int qr) {
	if (ql <= l && qr >= r) return tr[x].hsum;
	LL res = 0; down(x);
	if (ql <= m) res += qry(x << 1, l, m, ql, qr);
	if (qr > m) res += qry(x << 1 | 1, m + 1, r, ql, qr);
	return res;
}
int get(int x, int l, int r, int p) {
	if (l == r) return tr[x].sum;
	down(x);
	if (p <= m) return get(x << 1, l, m, p);
	else return get(x << 1 | 1, m + 1, r, p);
}
#undef m
signed main() {
    ios :: sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	cin >> n >> q;
	for (int i = 1, x; i <= n; i++) {
		cin >> x; a[i] = x;
		if (f[x]) le[i] = f[x]; f[x] = i;
	}
	for (int i = 1, l, r, x, y; i <= q; i++) {
		cin >> l >> r >> x >> y;
		v[x - 1].push_back((que){-i, l, r});
		v[y].push_back((que){i, l, r});
	}
	build(1, 1, n);
	set <int> s;
	s.insert(0);
	for (int i = 1; i <= n; i++) {
		if (le[i]) {
			int j = le[i];
			auto it = s.find(j);
			int x = *(--it), y = i;
			it++;
			if (++it != s.end()) y = *(it);
//			cerr << i << " : " << j << " " << x << " " << y << "\n";
			cur = {y - j, 0, 0};
			cov(1, 1, n, x + 1, j);
			s.erase(j);
		}
		cur = {i, 0, 0};
		cov(1, 1, n, i, i);
//		for (int i = 1; i <= n; i++) cerr << get(1, 1, n, i) << " \n"[i == n];
		s.insert(i);
		cur = {0, 0, 1};
		cov(1, 1, n, 1, i);
		for (auto [z, l, r] : v[i]) {
			LL val = z > 0 ? 1 : -1;
			if (z < 0) z = -z;
			ans[z] += val * qry(1, 1, n, l, r);
		}
	}
	for (int i = 1; i <= q; i++) cout << ans[i] << "\n";
	return 0;
}
posted @ 2023-05-21 14:57  came11ia  阅读(29)  评论(0编辑  收藏  举报