[ONTAK2010] Peaks加强版 (kruskal重构树+主席树+倍增)

description

在Bytemountains有N座山峰,每座山峰有他的高度h_i

有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走

现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。

Input

第一行三个数N,M,Q

第二行N个数,第i个数为h_i

接下来M行,每行3个数a b c,表示从a到b有一条困难值为c的双向路径

接下来Q行,每行三个数v x k,表示一组询问

Output

对于每组询问,输出一个整数表示答案。

Sample Input

10 11 4
1 2 3 4 5 6 7 8 9 10
1 4 4
2 5 3
9 8 2
7 8 10
7 1 4
6 7 1 
6 4 8
2 1 5
10 8 10
3 4 7
3 4 6
1 5 2
1 5 6
1 5 8
8 9 2

Sample Output

6
1
-1
8

Hint

N < = 1 0 5 , M , Q < = 5 ∗ 1 0 5 , h i , c , x < = 1 0 9 N<=10^5, M,Q<=5*10^5,h_i,c,x<=10^9 N<=105,M,Q<=5105hi,c,x<=109

solution

困难值小于等于x,又是这种限制题,很容易联想到前几天才写的[NOI2018]归程

套路的,要对边权进行排序,(通常还会有一步离散化),然后建立主席树,每个 i i i版本的线段树表示边权 ≤ x i \le x_i xi的所有边存在的树/图

本题思想是一致的,但是实现不同

使用kruskal重构树

重构后,对树dfn编序

叶子节点就表示该节点的高度,非叶子节点则表示该点子树内最大边权

每次查询的最大边限制x,就可以从v开始倍增地在dfs树上跳边,直到跳到某个祖先节点存的值是最大的小于等于x的边权,此时再往上跳边权就超过了x的限制

所以v能到达的点就是这个祖先节点管辖的区间内的所有点

kruskal重构树,深度越小的点代表的边权越大;实现是从小往上逐渐构造出一棵MST

利用dfn序列的性质,一个点子树内dfn序是一段连续区间,可以用线段树维护

直接线段树里面查即可

具体而言:对每个点都建立权值线段树,对每个点的线段树版本可持久化,查祖先管辖区间内的值就是其管辖区间右端点版本减去其管辖区间左端点的前一个版本

code

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 500005
int n, m, Q, cnt;
vector < int > G[maxn];
struct edge { int u, v, w; } E[maxn];
struct node { int lson, rson, tot; } t[maxn * 30];
int St[maxn], Ed[maxn], fa[maxn], h[maxn], d[maxn], root[maxn], id[maxn], dfn[maxn];
int f[maxn][20];

int find( int x ) { return x == fa[x] ? x : fa[x] = find( fa[x] ); }

void dfs( int u, int p ) {
	dfn[St[u] = ++ cnt] = u, f[u][0] = p;
	for( int i = 1;i < 20;i ++ )
		f[u][i] = f[f[u][i - 1]][i - 1];
	for( auto v : G[u] ) dfs( v, u );
	Ed[u] = cnt;
}

void modify( int &now, int lst, int l, int r, int pos ) {
	t[now = ++ cnt] = t[lst];
	t[now].tot ++; 
	if( l == r ) return;
	int mid = ( l + r ) >> 1;
	if( pos <= mid ) modify( t[now].lson, t[lst].lson, l, mid, pos );
	else modify( t[now].rson, t[lst].rson, mid + 1, r, pos );
}

int query( int L, int R, int l, int r, int k ) {
	if( l == r ) return l;
	int x = t[t[R].lson].tot - t[t[L].lson].tot;
	int mid = ( l + r ) >> 1;
	if( k <= x ) return query( t[L].lson, t[R].lson, l, mid, k );
	else return query( t[L].rson, t[R].rson, mid + 1, r, k - x );
}

int main() {
	scanf( "%d %d %d", &n, &m, &Q );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &h[i] ), d[i] = h[i];
	sort( d + 1, d + n + 1 );
	int tot = unique( d + 1, d + n + 1 ) - d - 1;
	for( int i = 1;i <= n;i ++ )
		h[i] = lower_bound( d + 1, d + tot + 1, h[i] ) - d;
	for( int i = 1;i <= m;i ++ )
		scanf( "%d %d %d", &E[i].u, &E[i].v, &E[i].w );
	sort( E + 1, E + m + 1, []( edge x, edge y ) { return x.w < y.w; } );
	for( int i = 1;i <= n;i ++ ) id[i] = fa[i] = i;
	int N = n;
	for( int i = 1;i <= m;i ++ ) {
		int u = find( E[i].u ), v = find( E[i].v ), w = E[i].w;
		if( u ^ v ) {
			h[++ N] = w;
			G[N].push_back( id[u] );
			G[N].push_back( id[v] );
			id[fa[v] = u] = N;
		}
	}
	dfs( N, N );
	for( int i = 1;i <= N;i ++ )
		if( dfn[i] <= n ) modify( root[i], root[i - 1], 1, tot, h[dfn[i]] );
		else root[i] = root[i - 1];
	int lastans = -1, v, x, k;
	while( Q -- ) {
		scanf( "%d %d %d", &v, &x, &k );
		if( ~ lastans ) v ^= lastans, x ^= lastans, k ^= lastans;
		for( int i = 19;~ i;i -- )
			if( h[f[v][i]] <= x ) v = f[v][i];
		x = t[root[Ed[v]]].tot - t[root[St[v] - 1]].tot;
		if( x < k )
			x = -1;
		else
			x = query( root[St[v] - 1], root[Ed[v]], 1, tot, x - k + 1 );
		printf( "%d\n", lastans = ( ~ x ) ? d[x] : -1 );
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值