【2019南昌邀请赛网络赛 J】Distance on the tree【边权树剖+主席树】

本文探讨了在给定带权边的树结构中,如何高效处理多次查询,即求解两点间路径上边权小于特定值的边数。通过离线操作与排序优化,采用树剖与主席树技术实现快速查询,特别关注边权树剖技巧,以提高算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给定一棵nnn个节点的树,每条边都有一个权值,mmm次查询,每次询问树上两点路径上边权小于kkk的边有多少条?(1≤n,m≤105)(1\leq n,m\leq 10^5)(1n,m105)


思路:

比较裸的题目,可以离线操作,然后将询问值从小到大进行排序,然后每次单点修改,将比当前询问小的边加入树中。

此处需要注意是边权树剖,因此将每条边的权值压到深度更深的节点上,然后树剖路径查询时,最后两点处于同一条链时,将深度最浅的点改为该点的儿子即可。

比赛时没有考虑到将询问值排序,然后依次修改边权,因此采用了树上主席树的做法。先将所有的边都插入树中,然后直接查询区间中小于k的节点有多少个即可。


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
const int N = 1e5+100;
using namespace std;

struct Edge { int next,to,w;} e[2*N];
struct Node { int l,r,ls,rs,sum;} t[20*N];
int n,m,rt,mod,val[N],head[N],tot,fa[N],d[N],son[N],size[N],top[N],id[N],rk[N],root[N],rot;
//top[x]: x节点所在链的顶端节点, id[x]: 节点dfs序, rk[x]: dfs序对应的节点
//val[x]: 每个点初始权值, fa[x]: 每个点父节点, d[x]: 节点深度, size[x]: 节点子树大小
//rt: 线段树根节点编号
void init(){
	memset(head,0,sizeof head);
	tot = 1, size[0] = 0;
}

void add(int x, int y, int z){
	e[++tot].next = head[x], e[tot].to = y, head[x] = tot, e[tot].w = z;
}

void dfs1(int x){	//求出每个点的子树大小、深度、重儿子
	size[x] = 1, d[x] = d[fa[x]]+1, son[x] = 0;
	for(int v,i = head[x]; i; i = e[i].next)
		if((v = e[i].to)!=fa[x]){
			fa[v] = x, dfs1(v), size[x] += size[v];
			if(size[son[x]] < size[v])
				son[x] = v;
		}
}

void dfs2(int x, int tp){	//求出每个节点的dfs序, dfs序对应的节点, 以及每个点所在链的顶端节点
	top[x] = tp, id[x] = ++tot, rk[tot] = x;
	if(son[x]) dfs2(son[x],tp);
	for(int v,i = head[x]; i; i = e[i].next)
		if((v = e[i].to)!=fa[x] && v!=son[x]) dfs2(v,v);
}

inline void pushup(int x){ //基础的线段树向上区间合并
	t[x].sum = t[t[x].ls].sum+t[t[x].rs].sum;
}

int build(int l,int r) //主席树建树部分
{
	int p = ++tot; // 新建一个节点,编号为p,代表当前区间[l,r]
	t[p].l = l, t[p].r = r, t[p].sum = 0;
	if(l == r) return p;
	int mid = (l+r)>>1;
	t[p].ls = build(l,mid);
	t[p].rs = build(mid+1,r);
	return p;
}

inline int len(int x) { return t[x].r-t[x].l+1; }

int ask(int lp,int rp,int k){ //主席树查询[lp,rp]比k小的有多少个
	if(t[lp].l == t[lp].r) return (t[rp].sum-t[lp].sum);
	int mid = (t[lp].l+t[lp].r)>>1;
	int tp = 0;
	if(mid < k){
		tp += (t[t[rp].ls].sum-t[t[lp].ls].sum);
		tp += ask(t[lp].rs,t[rp].rs,k);
	} 
	else tp += ask(t[lp].ls,t[rp].ls,k);
	return tp;
}

inline int solve(int x,int y,int k){
	int ret = 0;
	while(top[x] != top[y]){ //让x与y到达同一条链
		if(d[top[x]] < d[top[y]]) swap(x,y); //找到更深的点
		int xx = id[top[x]]-1, yy = id[x];
		int hp = ask(root[xx],root[yy],k);
		ret += hp;
		x = fa[top[x]];
	}
	if(x == y) return ret;
	if(id[x] > id[y]) swap(x,y);
	int xx = id[son[x]]-1, yy = id[y]; //边权树链剖分, 最后两点同链之后,取最高点的儿子来计算
	//-1是因为主席树要获取-1时候的副本进行sum减运算
	int hp = ask(root[xx],root[yy],k);
	return (ret+hp);
}

int insert(int now,int pos,int k) //主席树插入一个新的值
{
	int p = ++tot;
	t[p] = t[now]; //建立副本
	if(t[p].l == t[p].r){
		t[p].sum += k; //在副本上修改
		return p;
	}
	int mid = (t[p].l+t[p].r)>>1;
	if(pos <= mid) t[p].ls = insert(t[p].ls,pos,k); //保留右儿子部分,把左儿子更新
	else t[p].rs = insert(t[p].rs,pos,k);
	t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
	return p;
}

int ed[2*N][3];
int b[2*N],tb;

signed main()
{
	scanf("%d%d",&n,&m);
	init();
	rep(i,1,n-1){
		int x,y,z; scanf("%d %d %d",&x,&y,&z);
		add(x,y,z), add(y,x,z);
		ed[i][0] = x, ed[i][1] = y, ed[i][2] = z;
	}
	rot = 1;
	tot = 0, dfs1(rot), dfs2(rot, rot);
	rep(i,1,n-1){
		if(d[ed[i][0]] > d[ed[i][1]]) swap(ed[i][0],ed[i][1]);
		val[ed[i][1]] = ed[i][2];
		b[++tb] = ed[i][2];
	}	
	sort(b+1,b+1+tb);
	tb = unique(b+1,b+1+tb)-b-1;
	tot = 0;
	root[0] = build(1,tb);
	rep(i,1,n){
		int x = lower_bound(b+1,b+1+tb,val[rk[i]])-b;
		root[i] = insert(root[i-1],x,1);
	}
	rep(i,1,m){
		int u,v,k; scanf("%d%d%d",&u,&v,&k);
		int pos = upper_bound(b+1,b+1+tb,k)-b;
		pos--;
		if(pos == 0) printf("0\n");
		else printf("%d\n",solve(u,v,pos));
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值