SPOJ COT 树上简单路区间第k大(主席树 + lca)

题目

题意

给出一棵 n n n 个节点的数,每个节点有一个权值。之后有 m m m 次询问,每次询问 u , v , k u, v, k u,v,k 表示,树上 u u u k k k 的简单路径上,第 k k k 大的权值是多少

思路

一道图论+数据结构的特别有意思的题目,充分利用了主席树可减的特点。我们查询简单路径 ( u , v ) (u, v) (u,v) 时,可以通过 d f s dfs dfs 序建立主席树。而路径路径上的信息就是 T [ u ] + T [ v ] − T [ l c a ( u , v ) ] − T [ f a [ l c a ( u , v ) ] ] T[u] + T[v] - T[lca(u,v)] - T[fa[lca(u,v)]] T[u]+T[v]T[lca(u,v)]T[fa[lca(u,v)]]。需要注意的是, l c a ( u , v ) lca(u,v) lca(u,v) 只减一次,第二次减的是最近公共祖先的父亲节点。由于减了两遍 l c a lca lca 错了两发

代码

const int maxm = 20;
vector<int> G[maxn];
vector<int> v;
int fa[maxn][maxm], dep[maxn];
int vis[maxn], dis[maxn];
int mp[maxn];
struct Node{int ls, rs, sum;}T[maxn*40];
int root[maxn], tot;
int num[maxn], sz, n, m;

int getid(int x){return lower_bound(all(v), x) - v.begin() + 1;}

int build(int l, int r){
	int p = ++tot;
	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;
}

void update(int l, int r, int &x, int y, int pos){
	x = ++tot;
	T[x] = T[y];
	T[x].sum++;

	if(l == r) return ;
	int mid = (l + r) >> 1;
	if(mid >= pos) update(l, mid, T[x].ls, T[y].ls, pos);
	else update(mid+1, r, T[x].rs, T[y].rs, pos);
}

void dfs(int x){
	vis[x] = 1;
	rep(i, 1, maxm) fa[x][i] = fa[fa[x][i-1]][i-1];
	rep(i, 0, G[x].size()){
		if(!vis[G[x][i]]){
			dep[G[x][i]] = dep[x] + 1;
			dis[G[x][i]] = dis[x] + 1;
			fa[G[x][i]][0] = x;
			update(1, n, root[G[x][i]], root[x], getid(num[G[x][i]]));
			dfs(G[x][i]);
		}
	}
}

int query(int a, int b){
	if(dep[a] > dep[b]) swap(a, b);
	int tmp = dep[b] - dep[a];
	for(int i=0; (1<<i)<=tmp; i++)
		if((1<<i)&tmp) b = fa[b][i];
	per(i, 0, maxm) 
		if(fa[a][i] != fa[b][i])
			a = fa[a][i], b = fa[b][i];
	int lca = (a==b) ? a : fa[a][0];
	return lca;
}

int ask(int l, int r, int x, int y, int z, int faz, int k){
	if(l == r) return v[l - 1];
	int mid = (l + r) >> 1;
	int sum = T[T[x].ls].sum + T[T[y].ls].sum - T[T[z].ls].sum - T[T[faz].ls].sum;

	if(sum >= k) return ask(l, mid, T[x].ls, T[y].ls, T[z].ls, T[faz].ls, k);
	else return ask(mid+1, r, T[x].rs, T[y].rs, T[z].rs, T[faz].rs, k - sum);

}

int main()
{
    sdd(n, m);
    rep(i, 1, n+1)	sd(num[i]), v.pb(num[i]);
    sort(all(v));
    v.erase(unique(all(v)), v.end());

    rep(i, 1, n){
    	int u, v; sdd(u, v);
    	G[u].pb(v); G[v].pb(u);
    }

    root[0] = build(1, n); fa[1][0] = 0;
    update(1, n, root[1], root[0], getid(num[1]));
    dfs(1);

    rep(i, 0, m){
    	int u, v, k; sddd(u, v, k);
    	int w = query(u, v);
    	// cout<<u<<" "<<v<<" ** "<<w<<endl;
    	pd(ask(1, n, root[u], root[v], root[w], root[fa[w][0]], k));
    }

    return 0;    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]中的描述,p1168问题使用了线段解法。在构造的过程中,需要遍历整棵,所以时间复杂度为O(n)。但是在改变一个元素的值时,时间复杂度只有O(log(n))。求和的时候,的节点表示一个索引范围内元素值之和,只要将区间分割对应上,平均时间复杂度是O(log(n)),最坏情况下不会超过O(n*log(n))。\[1\] 根据引用\[2\]中的描述,QUERY 5 12应该是第4、5条边的极大值。\[2\] 根据引用\[3\]中的描述,代码中的if(L<=MID)和else if(R>MID)的判断条件是为了确保查询范围在左子或右子中。如果加上else,会导致错误。\[3\] #### 引用[.reference_title] - *1* [leetCode307:线段解法](https://blog.csdn.net/cyd1999/article/details/123963164)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Spoj 375 Qtree 链剖分 + 线段 解法](https://blog.csdn.net/niuox/article/details/8145842)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值