230928 做题记录 // 超级 NB 线段树

最近特别喜欢用 NB 这个词。这是为什么呢?

因为我太 NB 了。我怎么这么厉害呢?我好想朝所有人都嘚瑟嘚瑟!我真 NB!

先开题吧。


A - 等差子序列

https://vjudge.net/contest/583230#problem/A

非常 NB 的一道线段树!但是现在没空所以先不写。


B - 颜色

https://vjudge.net/contest/583230#problem/B

颜色删完过后剩下的肯定是一段区间。

那么区间外的所有颜色都会被删掉,如果要满足题目条件的话,删掉的颜色不能出现在区间内。

那么就可以有这么一个题意的转化:寻找区间的个数,满足区间内的颜色只出现在区间内。

然后你可能就要问了,不是还要满足区间外的所有颜色都不出现在区间内吗。但是你想想,要是它出现在区间内了,它作为区间内的颜色,不就不满足我们上面说的那条规则了吗。

这个转化是非常厉害的。那么这个时候有一个显而易见暴力做法,我们记录一个颜色在整个序列中出现的第一个位置(记为 Lx)和最后一个位置(记作 Rx),然后枚举每一个区间 [i,j],再枚举其中的每一个颜色,看看有没有超出去就好,复杂度 O(n3)

对纯暴力的一点小优化 上述区间内枚举过程转化为判定区间是否满足 LxminiRxmaxj,采用数据结构维护,就可以优化到 O(n2log)。为什么要专门提一嘴这个呢,因为这个模型我没想到。我真 NB。

接下来又是一个我想不到的模型。我们发现复杂度瓶颈出在枚举区间上,所以考虑通过固定区间右端点,用较小的复杂度直接求解满足条件的左端点数量来解决问题。为什么不是固定左端点呢?

「因为题解都是写的固定右端点。」 0# 如是说。

对于正在枚举的右端点 j 右边的颜色 x,我们记录它们上一次出现的位置 px,并用线段树找到范围内最右值 (px)max,那么左端点 i>(px)max。取 i=(px)max+1,这样我们就初步得到了一个 [i,j]。相对于纯暴力的做法,Rxj 的等价条件已经满足,但还有一个条件,就是 Lx 不能小于 i

为了方便数据结构维护 px,我们逆序枚举 j,这样又可以得到一个性质:i 单调不降。这个时候我们逆向思维,处理出对于每个 ii,其能够到的最远的 j,记为 fi,那么我们对于 fi 建一个权值线段树,然后在枚举过程中查询权值在 [j,+)i 的个数就是答案。由于求的是个数,所以可以对超出范围的 i 对应的 fi 进行删除操作。

那么 fi 又该怎么求呢?暴力地再建一个权值线段树维护 Lx,在 (,i) 权值范围内查询下标 k 的最小值,此时的 k 就是 fi

因为 0# 讲课的时候我在开飞机,所以我也不知道 0# 是不是这么讲的,总之我这么做应该能做出来,就是要维护的东西实在有亿点点多。

但是注意到一个线段树和两个权值线段树维护的大区间其实是一样的,所以我们只用一个线段树同时维护三个信息就好。最后时间复杂度 O(nlogn)

namespace XSC062 {
using namespace fastIO;
#define lt (p << 1)
#define rt (lt | 1)
#define mid (t[p].l + t[p].r) / 2
const int maxn = 3e5 + 5;
// shaber monotonicity
// destroy my youth 
struct _ { int l, r, u, d; };
struct __ {
	int u, i;
	__() {}
	__(int u1, int i1) {
		u = u1, i = i1;
	}
};
int T, n, res;
_ t[maxn << 2];
int a[maxn], L[maxn], R[maxn];
int max(int x, int y) {
	return x > y ? x : y;
}
int min(int x, int y) {
	return x < y ? x : y;
}
void pushup(int p) {
	t[p].u = t[lt].u + t[rt].u;
	return;
}
void pushdown(int p) {
	if (t[p].d) {
		t[lt].d = t[rt].d = 1;
		t[lt].u = t[lt].r - t[lt].l + 1;
		t[rt].u = t[rt].r - t[rt].l + 1;
		t[p].d = 0;
	}
	return;
}
void bld(int p, int l, int r) {
	t[p].u = t[p].d = 0;
	t[p].l = l, t[p].r = r;
	if (l == r) return;
	bld(lt, l, mid), bld(rt, mid + 1, r);
	return;
}
void add(int p, int l, int r) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].u = t[p].r - t[p].l + 1;
		t[p].d = 1;
		return;
	}
	pushdown(p);
	if (l <= mid) add(lt, l, r);
	if (r > mid) add(rt, l, r);
	pushup(p);
	return;
}
int ask(int p, int l, int r) {
	if (l <= t[p].l && t[p].r <= r)
		return t[p].u;
	int res = 0; pushdown(p);
	if (l <= mid) res = ask(lt, l, r);
	if (r > mid) res += ask(rt, l, r);
	return res;
}
int main() {
	read(T);
	while (T--) {
		read(n), res = 0;
		std::stack<__> p;
		std::fill(L + 1, L + n + 1, n + 5);
		std::fill(R + 1, R + n + 1, 0);
		for (int i = 1; i <= n; ++i) {
			read(a[i]), R[a[i]] = i;
			if (L[a[i]] == n + 5) L[a[i]] = i;
		}
		bld(1, 1, n);
		for (int i = 1, j; i <= n; ++i) {
			if (i == R[a[i]] && i != L[a[i]])
				add(1, L[a[i]] + 1, R[a[i]]);
			else p.push(__(a[i], i));
			while (!p.empty() && R[p.top().u] <= i) p.pop();
			j = p.size() ? p.top().i : 0;
			if (i != j)
				res += i - j - ask(1, j + 1, i);
		}
		print(res, '\n');
	}
	return 0;
}
} // namespace XSC062
posted @   XSC062  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示