题解:P11982 [KTSC 2021] 路灯 / streetlight

题意

给定长度为 \(n\) 的序列 \(a\)。有 \(q\) 次单点修改,在所有修改前和每次修改后,求有多少对 \((i,j)\) 满足:

  • \(a_i=a_j\)
  • \(\forall i<k<j,a_k<a_i\)

\(n\leq 10^5\)\(q\leq 2.5\times 10^5\)

题解

KTSC 好难啊!!!

为了方便,令 \(a_0=a_{n+1}=\infty\)。考察所有合法的区间 \([i,j]\),我们发现这些区间两两之间要么不交(不考虑端点相交),要么成包含关系,于是这些区间构成了一棵树,根节点为 \([0,n+1]\)

考察修改操作带来的影响,我们发现单点增加比单点减小性质好很多。因为单点增加 \(a_x\),只会破坏原有的合法区间,并新增极少的区间;而单点减小会导致大量原本不合法的区间突然合并。考虑通过对时间分治全部转化成单点加的情况。具体来说,分治到时间区间 \([L,R]\) 时,把区间内的变化位置设为 \(0\)。考虑 \([mid+1,R]\) 内没有出现在 \([L,mid]\) 内的变化位置,则把这些位置增加到初始值,递归到 \([L,mid]\),然后撤销先前的操作。再考虑 \([L,mid]\) 内没有出现在 \([mid+1,R]\) 内的变化位置,把这些位置增加到 \(mid\) 时刻结束时的值,递归到 \([mid+1,R]\)

刻画将 \(a_x\) 单点增加到 \(v\) 的影响:

  • 删掉所有包含 \(x\) 且满足 \(a_l\leq a_x\) 的区间 \([l,r]\)
  • 考虑 \(x\) 前面第一个 \(\geq v\) 的位置 \(l\),若 \(a_l=v\) 则添加区间 \([l,x]\)
  • 考虑 \(x\) 后面第一个 \(\geq v\) 的位置 \(r\),若 \(a_r=v\) 则添加区间 \([x,r]\)

放到树上刻画这个事情,可以发现删去的区间是一条祖先链,然后最顶部被删去的区间有可能分裂成新添加的两个区间。

一处细节

考虑对 \(a_x\) 单点加时,如果之前同时存在区间 \([l,x],[x,r]\),则这两个区间都会被删去,那删去的区间就并不是一条祖先链了。如果只是任意位置单点加,这种情况是有可能发生的。但是在我们的分治结构下,这种情况是不会发生的。这是因为,如果我们对某个位置进行了单点加操作,则该位置必然不会在接下来递归下去的区间中出现,这样就不会删去以 \(x\) 为端点的区间了。

考虑直接维护这棵树。区间形成的树结构有一个很好的性质:将所有区间按左端点从小到大排序即可得到这棵树的 DFS 序。因此每次进行一系列单点加操作时,可以把操作序列按操作位置从小到大排序,在 DFS 序上扫描线,用单调栈维护当前点的根链,可以直接找出包含当前点的最小区间,也就是删去的祖先链的链底,然后在单调栈上二分找出删去的祖先链的链顶。线段树二分一下找出新增区间,然后和所有留下来的区间放到一起再排个序就可以了。设树的点数为 \(V\),这样我们可以 \(\mathcal{O}((V+q)\log{V})\) 地处理一个长度为 \(q\) 的操作序列。注意到分治过程中树的大小是没有保证的,因此复杂度可以被卡到 \(\mathcal{O}((n+q)^2\log{(n+q)})\)

尝试压缩树的点数。考虑比较智慧的一步,对于当前区间内的每个操作,设它会删去树上一条从 \(u\)\(v\) 的祖先链(\(dep_u>dep_v\)),则我们将 \(u\)\(fa_v\) 加入关键点集合中。对关键点建出虚树,那么我们直接维护这个虚树即可。建出虚树后可能会有一些剩余的节点,这些节点一定不会被删去,直接计入答案即可。可以结合下面的图片理解,红点是关键点,蓝边是虚树上的边,橙色区域框起来的点就是剩余节点,我们将其计入 \(cnt\),直接累加到递归区间的答案中。

显然虚树中点数是 \(\mathcal{O}(R-L+1)\) 级别的,这样时间复杂度就优化到了 \(\mathcal{O}((n+q)\log^2(n+q))\)

建议配合代码理解。实现起来细节很多。

主要代码
int n, q, a[N];
pii op[N + Q];
int lst[N + Q], prv[N + Q], nxt[N + Q];
ll ans[N + Q];

struct Node {
	int l, r, v, c;
	friend bool operator<(const Node &lhs, const Node &rhs) { return lhs.l < rhs.l; }
	friend bool operator==(const Node &lhs, const Node &rhs) { return lhs.l == rhs.l; }
};

Node T[LOGN][N + Q], tmp[N + Q];
int szt[LOGN], sz_tmp;
int sz_upd, upd[N + Q];
int sz_key;
pii key[N + Q];
int top, stk[N + Q];
ll S, sum[N + Q];

struct SegTree {
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
	int m, mx[N << 2];
	void init() { m = 1 << 32 - __builtin_clz(n - 1); }
	void upd(int x, int v) {
		mx[x += m - 1] = v;
		while (x > 1) x >>= 1, mx[x] = max(mx[ls(x)], mx[rs(x)]);
	}
	int find_l(int r, int v) {
		if (!r) return 0;
		r += m - 1;
		while (1) {
			r >>= __builtin_ctz(~r);
			if (mx[r] >= v) {
				while (r < m) r = mx[rs(r)] >= v ? rs(r) : ls(r);
				return r - m + 1;
			}
			if (r == lowbit(r)) break;
			--r;
		}
		return 0;
	}
	int find_r(int l, int v) {
		if (l == n + 1) return n + 1;
		l += m - 1;
		while (1) {
			l >>= __builtin_ctz(l);
			if (mx[l] >= v) {
				while (l < m) l = mx[ls(l)] >= v ? ls(l) : rs(l);
				return min(l - m + 1, n + 1);
			}
			++l;
			if (l == lowbit(l)) break;
		}
		return n + 1;
	}
	int prv(int x) {
		int p = find_l(x - 1, a[x]);
		return !p || a[p] > a[x] ? 0 : p;
	}
	int nxt(int x) {
		int p = find_r(x + 1, a[x]);
		return p == n + 1 || a[p] > a[x] ? n + 1 : p;
	}
#undef ls
#undef rs
} sgt;

void apply_op(int dep) {
	Node *T = ::T[dep], *nd = tmp;
	int sz_cur = szt[dep], &sz_nxt = sz_tmp;
	int p = 0;
	top = sz_nxt = 0;
	for (int i = 0; i < sz_upd; ++i) {
		int x = upd[i];
		sgt.upd(x, a[x]);
		while (p < sz_cur && T[p].l <= x) {
			while (top && T[stk[top]].r <= T[p].r) nd[sz_nxt++] = T[stk[top--]];
			stk[++top] = p++;
		}
		while (top && T[stk[top]].r < x) nd[sz_nxt++] = T[stk[top--]];
		while (top && T[stk[top]].v <= a[x]) --top;
	}
	while (top) nd[sz_nxt++] = T[stk[top--]];
	while (p < sz_cur) nd[sz_nxt++] = T[p++];
	for (int i = 0; i < sz_upd; ++i) {
		int x = upd[i];
		int l = sgt.prv(x);
		if (l) nd[sz_nxt++] = {l, x, a[x], 1};
		int r = sgt.nxt(x);
		if (r <= n) nd[sz_nxt++] = {x, r, a[x], 1};
	}
	sort(nd, nd + sz_nxt), sz_nxt = unique(nd, nd + sz_nxt) - nd;
	top = S = 0;
	for (int i = 0; i < sz_nxt; ++i) {
		while (top && nd[stk[top]].r <= nd[i].r) --top;
		sum[i] = (top ? sum[stk[top]] : 0) + nd[i].c;
		S += nd[i].c;
		stk[++top] = i;
	}
}

void build(int dep) {
	Node *T = tmp, *nd = ::T[dep + 1];
	int sz_cur = sz_tmp, &sz_nxt = szt[dep + 1];
	static int V[N + Q << 1];
	int p = 0, szv = 0;
	V[szv++] = 0;
	top = 0;
	for (int i = 0; i < sz_key; ++i) {
		auto [x, v] = key[i];
		while (p < sz_cur && T[p].l <= x) {
			while (top && T[stk[top]].r <= T[p].r) --top;
			stk[++top] = p++;
		}
		while (top && T[stk[top]].r < x) --top;
		V[szv++] = stk[top];
		int l = 1, r = top, mid;
		while (l < r) T[stk[mid = l + r + 1 >> 1]].v > v ? l = mid : r = mid - 1;
		V[szv++] = stk[l];
	}
	sort(V, V + szv), szv = unique(V, V + szv) - V;
	p = top = 0;
	for (int i = 0, sz = szv; i < sz - 1; ++i) {
		while (p <= V[i]) {
			while (top && T[stk[top]].r <= T[p].r) --top;
			stk[++top] = p++;
		}
		while (top && T[stk[top]].r <= T[V[i + 1]].r) --top;
		V[szv++] = stk[top];
	}
	sort(V, V + szv), szv = unique(V, V + szv) - V;
	top = 0, sz_nxt = szv;
	for (int i = 0; i < szv; ++i) {
		auto [l, r, v, c] = T[V[i]];
		while (top && T[stk[top]].r <= r) --top;
		c = sum[V[i]] - (top ? sum[stk[top]] : 0);
		S -= c;
		nd[i] = {l, r, v, c};
		stk[++top] = V[i];
	}
}

void solve(int l, int r, int dep) {
	auto proc = [&]() {
		sort(upd, upd + sz_upd), sort(key, key + sz_key);
		for (int i = 0; i < sz_key; ++i) sgt.upd(key[i].first, 0);
		apply_op(dep), build(dep);
	};
	if (l == r) {
		a[op[l].first] = op[l].second;
		upd[sz_upd++] = op[l].first;
		proc();
		ans[l] += S, ans[l + 1] -= S;
		return sz_upd = 0, void();
	}
	int mid = l + r >> 1;
	if (mid >= n) {
		for (int i = mid + 1; i <= r; ++i) if (prv[i] < l) upd[sz_upd++] = op[i].first;
		copy(op + l, op + mid + 1, key), sz_key = mid - l + 1;
		for (int i = l; i <= mid; ++i) if (prv[i] && prv[i] < l) key[sz_key++] = op[prv[i]];
		proc();
		ans[l] += S, ans[mid + 1] -= S;
		sz_upd = sz_key = 0;
		solve(l, mid, dep + 1);
	}
	for (int i = l; i <= mid; ++i) if (nxt[i] > r) upd[sz_upd++] = op[i].first;
	copy(op + mid + 1, op + r + 1, key), sz_key = r - mid;
	for (int i = mid + 1; i <= r; ++i) if (prv[i] && prv[i] <= mid) key[sz_key++] = op[prv[i]];
	proc();
	ans[mid + 1] += S, ans[r + 1] -= S;
	sz_upd = sz_key = 0;
	solve(mid + 1, r, dep + 1);
}
posted @ 2026-02-20 23:16  P2441M  阅读(6)  评论(0)    收藏  举报