P4197 Peaks 题解

P4197 Peaks

题面:

题目描述

在 Bytemountains 有 n n n 座山峰,每座山峰有他的高度 h i h_i hi。有些山峰之间有双向道路相连,共 m m m 条路径,每条路径有一个困难值,这个值越大表示越难>走。

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

输入格式

第一行三个数 n , m , q n,m,q n,m,q
第二行 n n n 个数,第 i i i 个数为 h i h_i hi

接下来 m m m 行,每行三个整数 a , b , c a,b,c a,b,c,表示从 a → b a \to b ab 有一条困难值为 c c c 的双向路径。
接下来 q q q 行,每行三个数 v , x , k v,x,k v,x,k,表示一组询问。

输出格式

对于每组询问,输出一个整数表示能到达的山峰中第 k k k 高的山峰的高度。

样例 #1
样例输入 #1
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
样例输出 #1
6
1
-1
8
提示
数据规模与约定

对于 100 % 100\% 100% 的数据, n ≤ 1 0 5 n \le 10^5 n105 0 ≤ m , q ≤ 5 × 1 0 5 0 \le m,q \le 5\times 10^5 0m,q5×105 h i , c , x ≤ 1 0 9 h_i,c,x \le 10^9 hi,c,x109

不难看出本题是 Kruskal 重构树

建出 Kruskal 最小树重构树后倍增找到可达的最远的 lca ,

然后,对于子树中的叶子进行第 k k k 大查询!

然而叶子的编号不是连续的,我们很难用什么数据结构维护。

考虑改变编号,我们用 dfs 序去遍历树,如图:

img

我们发现,因为 dfs 序的性质,一个节点的子树被访问完了之后,才会退出该节点,所以一个子树的叶子节点一定构成一个连续区间,

这样我们就将题目变成了区间第 k k k 大查询,用可持久化线段树就可以维护出来了.

因为可持久化线段树维护的值可以离散化,果断离散化减少空间

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 1e5+5,M = N * 16,maxn = N * 16;
int n,m,q,h[maxn],s[maxn],k,v[maxn];
array<int,3> e[M];
int head[maxn],nxt[maxn<<1],to[maxn<<1],cnt;
void init() {memset(head,-1,sizeof(head));}
int find(int x) {
	if(s[x] ^ x) s[x] = find(s[x]);
	return s[x];
}
void add(int u,int V) {
	nxt[cnt] = head[u];
	to[cnt] = V;
	head[u] = cnt++;
}

void kruskal() {
	k = n;
	int tot = 0;
	for(int i = 0;i<maxn;i++) s[i] = i;
	sort(e + 1,e + m + 1);
	for(int i = 1;i<=m;i++) {
		int fx = find(e[i][1]),fy = find(e[i][2]);
		if(fx ^ fy) {
			s[fx] = s[fy] = ++k;
			add(fx,k);add(fy,k);
			add(k,fx);add(k,fy);
			v[k] = e[i][0];
			tot++;
			if(tot == n - 1) break;
		}
	}
}

int fa[maxn][21],hd[maxn][21],L[maxn],R[maxn],tot;
int rt[maxn],lst[maxn];
namespace sgt{
#define mid ((pl + pr) >> 1)
int rot,siz[N*50],ls[N*50],rs[N*50];
int update(int f,int pl,int pr,int x) {
	int rt = ++rot;
	ls[rt] = ls[f];
	rs[rt] = rs[f];
	siz[rt] = siz[f] + 1;
	if(pl < pr) {
		if(x <= mid) ls[rt] = update(ls[f],pl,mid,x);
		else rs[rt] = update(rs[f],mid+1,pr,x);
	}
	return rt;
}

int query(int x,int y,int pl,int pr,int K) {
	if(siz[y] - siz[x] < K) return -1;
	if(pl == pr) return pl;
	int sz = siz[rs[y]] - siz[rs[x]];
	if(K <= sz) return query(rs[x],rs[y],mid + 1,pr,K);
	else return query(ls[x],ls[y],pl,mid,K - sz);
}

}

void dfs(int x,int f) {
	fa[x][0] = f;hd[x][0] = v[fa[x][0]];
	for(int i = 1;i<=20;i++) {
		fa[x][i] = fa[fa[x][i - 1]][i - 1]; 
		hd[x][i] = min(hd[fa[x][i - 1]][i - 1],hd[x][i]);
	}
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ f) {
			dfs(y,x);
			L[x] = min(L[x],L[y]);
			R[x] = max(R[x],R[y]);
		}
	}
	if(x <= n) {
		R[x] = L[x] = ++tot;
		rt[tot] = sgt::update(rt[tot - 1],1,N,h[x]);
	}
}


signed main() {
	init();
    n = rd(),m = rd(),q = rd();
	for(int i = 1;i<=n;i++) lst[i] = h[i] = rd();
	for(int i = 1;i<=m;i++) 
		e[i][1] = rd(),e[i][2] = rd(),e[i][0] = rd();
	sort(lst + 1,lst + n + 1);
	int top = unique(lst + 1,lst + n + 1) - lst - 1;
	for(int i = 1;i<=n;i++)
		h[i] = lower_bound(lst + 1,lst + top + 1,h[i]) - lst;
	kruskal();
	memset(L,0x3f,sizeof(L));
	memset(hd,0x3f,sizeof(hd));
	v[0] = 0x3f3f3f3f3f3f3f3fLL;
	dfs(k,0);
	while(q--) {
		int S = rd(),x = rd(),K = rd();
		for(int i = 20;i >= 0;i--) 
			if(hd[S][i] <= x) 
				S = fa[S][i];
		if(sgt::siz[rt[R[S]]] - sgt::siz[rt[L[S] - 1]] < K) {puts("-1");continue;}
		int ans = sgt::query(rt[L[S] - 1],rt[R[S]],1,N,K);
		if(ans == -1) puts("-1");
		else wt(lst[ans]),putchar('\n');
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值