树的距离(dfs序+主席树)

题目链接
感受
主 席 树 模 板 题 , 确 实 简 单 , 但 比 那 个 第 k 大 模 板 稍 微 难 一 点 主席树模板题,确实简单,但比那个第k大模板稍微难一点 k
思路
该 问 题 就 转 化 为 某 个 子 树 中 , 子 节 点 到 该 子 树 根 节 点 的 距 离 > = k 的 距 离 和 该问题就转化为某个子树中,子节点到该子树根节点的距离\\>=k的距离和 >=k
由 于 子 树 有 很 多 , 于 是 对 于 距 离 我 们 可 以 进 行 一 下 转 化 。 由于子树有很多,于是对于距离我们可以进行一下转化。
即 d i s ( a , b ) = ∣ d i s ( a , 1 ) − d i s ( b , 1 ) ∣ 于 是 , 如 果 我 们 知 道 某 个 子 树 上 有 哪 些 节 点 , 并 把 这 些 节 点 单 独 处 理 , 可 以 做 一 棵 权 值 线 段 树 , 那 么 就 可 以 很 快 求 解 。 即dis(a, b)=|dis(a,1)-dis(b,1)|\\于是,如果我们知道某个子树上有哪些节点,并把这些节点\\单独处理,可以做一棵权值线段树,那么就可以很快求解。 dis(a,b)=dis(a,1)dis(b,1)线
子 树 可 以 联 想 到 d f s 序 , 某 一 个 子 树 + d f s 序 就 可 以 联 想 到 区 间 子树可以联想到dfs序,某一个子树+dfs序就可以联想到区间 dfs+dfs
于 是 , 主 席 树 就 诞 生 了 。 于是,主席树就诞生了。
我 们 可 以 先 求 出 这 棵 树 的 d f s 序 , 然 后 对 于 这 个 d f s 序 建 一 棵 主 席 树 , 其 中 每 个 节 点 维 护 到 该 时 刻 区 间 值 和 以 及 区 间 个 数 和 。 我们可以先求出这棵树的dfs序,然后对于这个dfs序建一棵\\主席树,其中每个节点维护到该时刻区间值和以及区间个数和。 dfsdfs
最 后 得 到 ( x 节 点 加 入 前 一 刻 , x 节 点 子 树 加 完 的 最 后 一 刻 ) 的 区 间 值 v a l 以 及 区 间 个 数 n u m a n s = v a l − n u m ∗ d i s [ x ] 最后得到(x节点加入前一刻,x节点子树加完的最后一刻)\\的区间值val以及区间个数num\\ans=val-num*dis[x] xxvalnumans=valnumdis[x]
总 体 来 说 就 是 , d f s 序 上 建 一 棵 主 席 树 总体来说就是,dfs序上建一棵主席树 dfs

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
struct edge{
	int v, nex; ll w;
}e[maxn << 1];
int head[maxn], cnt;
ll dis[maxn];
int in[maxn], out[maxn], a[maxn];///a[i] 表示dfs序中第i个点编号
ll sum[maxn << 5], num[maxn << 5];
int rt[maxn], lc[maxn << 5], rc[maxn << 5], tot;
vector<ll> b;
int n, now;
void add_edge(int u, int v, ll w){
	e[cnt] = (edge){v, head[u], w};
	head[u] = cnt++;
}
void init(){
	cnt = 0;
	for(int i = 1; i <= n; i++){
		head[i] = -1;
	}
}
void dfs(int u, int fa){
	int v; ll w; in[u] = ++cnt; a[cnt] = u;
	for(int i = head[u]; ~i; i = e[i].nex){
		v = e[i].v; w = e[i].w;
		if(v == fa) continue;
		dis[v] = dis[u] + w;
		dfs(v, u);
	}
	out[u] = cnt;
}
int get_id(ll x){
	return lower_bound(b.begin(), b.end(), x) - b.begin() + 1;
}
void build(int &o, int l, int r){
	o = ++tot; sum[o] = 0; num[o] = 0;
	if(l == r) return ;
	int mid = (l + r) / 2;
	build(lc[o], l, mid);
	build(rc[o], mid + 1, r);
}
int update(int o, int l, int r, int k){
	int oo = ++tot; sum[oo] = sum[o] + b[k - 1]; num[oo] = num[o] + 1;
	lc[oo] = lc[o], rc[oo] = rc[o];
	if(l == r) return oo;
	int mid = (l + r) / 2;
	if(mid >= k) lc[oo] = update(lc[o], l, mid, k);
	else rc[oo] = update(rc[o], mid + 1, r, k);
	return oo;
}
void query(int loo, int roo, int l, int r, int x, int y, ll &ans, ll &gg){
	if(l >= x && y >= r){
		ans += sum[roo] - sum[loo]; gg += num[roo] - num[loo];
		return ;
	}
	int mid = (l + r) / 2;
	if(mid >= x) query(lc[loo], lc[roo], l, mid, x, y, ans, gg);
	if(y > mid) query(rc[loo], rc[roo], mid + 1, r, x, y, ans, gg);
}
void print(int o, int l, int r){
	printf("[%d, %d] = %lld\n", l, r, sum[o]);
	if(l == r) return ;
	int mid = (l + r) / 2;
	print(lc[o], l, mid);
	print(rc[o], mid + 1, r);
}
int main(){
	scanf("%d", &n);
	init();
	int p; ll d;
	for(int i = 1; i < n; i++){
		scanf("%d%lld", &p, &d);
		add_edge(p, i + 1, d); add_edge(i + 1, p, d);
	}
	dis[1] = cnt = 0;
	dfs(1, 0);
	for(int i = 1; i <= n; i++){
		b.push_back(dis[i]);
	}
	sort(b.begin(), b.end()); b.erase(unique(b.begin(), b.end()), b.end());
	now = b.size();
	build(rt[0], 1, now);
	for(int i = 1; i <= n; i++){
		int pos = get_id(dis[a[i]]);
		rt[i] = update(rt[i - 1], 1, now, pos);
	}
	int q, x, k;
	scanf("%d", &q);
	while(q--){
		scanf("%d%lld", &x, &d);
		d += dis[x];
		k = get_id(d);///[k, now]
		if(k > now) puts("0");
		else{
			ll ans = 0, gg = 0;
			query(rt[in[x] - 1], rt[out[x]], 1, now, k, now, ans, gg);
			printf("%lld\n", ans - gg * dis[x]);
		}
	}
	return 0;
}
/*
5
1 5
2 3
2 4
1 10

100
5 0
*/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值