【LOJ2570】「ZJOI2017」线段树

【题目链接】

【思路要点】

  • 对于区间 [ l , r ] [l,r] [l,r] ,考虑 [ l − 1 , l − 1 ] [l-1,l-1] [l1,l1] 对应的点 x x x [ r + 1 , r + 1 ] [r+1,r+1] [r+1,r+1] 对应的点 y y y ,记 L c a Lca Lca x , y x,y x,y 的最近公共祖先。
  • x x x L c a Lca Lca 链上的某个点具有不在该链上的右儿子,则该右儿子会被定位到;若 y y y L c a Lca Lca 链上的某个点具有不在该链上的左儿子,则该左儿子会被定位到,并且不难发现以上节点恰好就是被定位到的所有节点。
  • 树上差分后按照 u u u 是否在 L c a Lca Lca 子树内分类讨论即可。
  • 时间复杂度 O ( N L o g N + M L o g N ) O(NLogN+MLogN) O(NLogN+MLogN)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 5;
const int MAXLOG = 20;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, tot, lc[MAXN], rc[MAXN], home[MAXN];
int root, depth[MAXN], father[MAXN][MAXLOG];
int build(int l, int r) {
	int pos = ++tot;
	if (l == r) {
		home[l] = pos;
		return pos;
	}
	int mid; read(mid);
	lc[pos] = build(l, mid);
	father[lc[pos]][0] = pos;
	rc[pos] = build(mid + 1, r);
	father[rc[pos]][0] = pos;
	return pos;
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (depth[father[x][i]] >= depth[y]) x = father[x][i];
	if (x == y) return x;
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (father[x][i] != father[y][i]) {
			x = father[x][i];
			y = father[y][i];
		}
	return father[x][0];
}
int climb(int x, int y) {
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (depth[father[x][i]] > depth[y]) x = father[x][i];
	return x;
}
void dfs(int pos, int fa) {
	depth[pos] = depth[fa] + 1;
	for (int i = 1; i < MAXLOG; i++)
		father[pos][i] = father[father[pos][i - 1]][i - 1];
	if (lc[pos]) dfs(lc[pos], pos);
	if (rc[pos]) dfs(rc[pos], pos);
}
pair <int, ll> lsum[MAXN], rsum[MAXN];
void work(int pos, int fa) {
	if (lc[pos]) {
		lsum[lc[pos]] = lsum[pos];
		rsum[lc[pos]] = rsum[pos];
		rsum[lc[pos]].first += 1;
		rsum[lc[pos]].second += depth[rc[pos]];
		work(lc[pos], pos);
	}
	if (rc[pos]) {
		lsum[rc[pos]] = lsum[pos];
		rsum[rc[pos]] = rsum[pos];
		lsum[rc[pos]].first += 1;
		lsum[rc[pos]].second += depth[lc[pos]];
		work(rc[pos], pos);
	}
}
int main() {
	read(n), build(1, n);
	home[0] = ++tot;
	lc[tot + 1] = tot;
	father[tot][0] = tot + 1;
	rc[tot + 1] = 1;
	father[1][0] = ++tot;
	
	home[n + 1] = ++tot;
	lc[tot + 1] = tot - 1;
	father[tot - 1][0] = tot + 1;
	rc[tot + 1] = tot;
	father[tot][0] = tot + 1;
	
	dfs(root = ++tot, 0);
	work(root, 0);
	
	read(m);
	for (int i = 1; i <= m; i++) {
		int u, l, r; read(u), read(l), read(r);
		l = home[l - 1], r = home[r + 1];
		int x = lca(l, r), tl = climb(l, x), tr = climb(r, x);
		ll ans = rsum[l].second - rsum[tl].second + lsum[r].second - lsum[tr].second;
		int cnt = rsum[l].first - rsum[tl].first + lsum[r].first - lsum[tr].first;
		ans += 1ll * cnt * depth[u];
		if (u == x || lca(u, x) != x) {
			int y = lca(u, x);
			ans -= 2ll * cnt * depth[y];
		} else if (lca(u, l) != x) {
			int y = lca(u, l);
			ans -= 2ll * (lsum[r].first - lsum[tr].first) * depth[x];
			ans -= 2ll * (rsum[l].first - rsum[y].first) * depth[y];
			ans -= 2ll * (rsum[y].second - rsum[tl].second - (rsum[y].first - rsum[tl].first));
			if (y != u && climb(u, y) == rc[y]) ans -= 2;
		} else {
			int y = lca(u, r);
			ans -= 2ll * (rsum[l].first - rsum[tl].first) * depth[x];
			ans -= 2ll * (lsum[r].first - lsum[y].first) * depth[y];
			ans -= 2ll * (lsum[y].second - lsum[tr].second - (lsum[y].first - lsum[tr].first));
			if (y != u && climb(u, y) == lc[y]) ans -= 2;
		}
		writeln(ans);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值