P4197 Peaks&&P7834 [ONTAK2010] Peaks 加强版

P4197 Peaks
P7834 [ONTAK2010] Peaks 加强版
在这里插入图片描述
加强版:就是多记录一个lastans,同时查询时v、x、k根据式子变换就行了

题意:每次从山v开始,经过困难值不超过x的路径所能到达的山峰中,第k高的山峰的高度。
思路:Kruskal重构树跑dfs序建主席树找第k大(0…0)

因为问题中是困难值不超过x的路径,也就是最大值(极限值)的最小值,所以我们用Kruskal重构树时是最小生成树;
随后就是再dfs序中找第k大值了,没有k个数字输出-1。上主席树就行了。

//https://www.luogu.com.cn/problem/P4197 kruskal重构树+主席树
#include<bits/stdc++.h>
using namespace std;
const int N=8e5+5;
int n,m,q,h[N];
int to[N],ne[N],val[N],fir[N],idx,cnt;
int fa[N],f[N][25];//树上倍增
int L[N],R[N],nu;//dfs序
int root[N],id,sz;//sz为离散化后num大小
vector<int>num;

struct node{int ls,rs,sum;}tr[N*30];//主席树
struct Edge{int u,v,w;}edge[N];
bool cmp(Edge l,Edge r){return l.w<r.w;}//最小生成树
void add(int u,int v){to[idx]=v;ne[idx]=fir[u];fir[u]=idx++;}//链式前
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}//并查集

void disc(){//离散化
	sort(num.begin(),num.end());
	num.erase(unique(num.begin(),num.end()),num.end());
	sz=num.size();
	for(int i=1;i<=n;i++) h[i]=lower_bound(num.begin(),num.end(),h[i])-num.begin()+1;
}

void input(){
	memset(fir,-1,sizeof(fir));
	scanf("%d%d%d",&n,&m,&q); cnt=n;//已经有n个点,下个节点编号从n+1起
	for(int i=1;i<=n;i++) scanf("%d",&h[i]),num.push_back(h[i]);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
}

void Kruskal(){
	sort(edge+1,edge+m+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int fu=find(edge[i].u),fv=find(edge[i].v),w=edge[i].w;
		if(fu==fv) continue;
		val[++cnt]=w;
		fa[cnt]=fa[fu]=fa[fv]=cnt;
		add(cnt,fu),add(cnt,fv);//节点cnt是fu、fv的父亲节点
	}
}

int build(int l,int r){
	int p=++id;
	if(l==r) return p;
	int mid=(l+r)>>1;
	tr[p].ls=build(l,mid),build(mid+1,r);
	return p;
}

int insert(int p,int l,int r,int x){//主席树板子,插数
	int q=++id;
	tr[q]=tr[p];
	if(l==r){
		tr[q].sum++;
		return q;
	}
	int mid=(l+r)>>1;
	if(x<=mid) tr[q].ls=insert(tr[p].ls,l,mid,x);
	else tr[q].rs=insert(tr[p].rs,mid+1,r,x);
	tr[q].sum=tr[tr[q].ls].sum+tr[tr[q].rs].sum;
	return q;
}

void dfs(int u,int father){
	f[u][0]=father;
	for(int i=1;i<=22;i++) f[u][i]=f[f[u][i-1]][i-1];
	L[u]=nu;
	if(fir[u]==-1){//叶子节点,就加入
		L[u]=++nu;
		root[nu]=insert(root[nu-1],1,sz,h[u]);
	}
	for(int i=fir[u];~i;i=ne[i]) dfs(to[i],u);
	R[u]=nu;
}

int query(int q,int p,int l,int r,int kth){//主席树板子,区间第k大
	if(l==r) return l;
	int sum=tr[tr[p].rs].sum-tr[tr[q].rs].sum;
	int mid=(l+r)>>1;
	if(kth<=sum) return query(tr[q].rs,tr[p].rs,mid+1,r,kth);
	else return query(tr[q].ls,tr[p].ls,l,mid,kth-sum);
}

void Ac(){
	root[0]=build(1,sz);
	dfs(cnt,0);
	while(q--){
		int v,x,k; scanf("%d%d%d",&v,&x,&k);
		for(int i=22;i>=0;i--){
			if(f[v][i]&&val[f[v][i]]<=x){//从x出发,也就是一颗包含x的子树,倍增找到子树的根
				v=f[v][i];
			}
		}
		if(tr[root[R[v]]].sum-tr[root[L[v]]].sum<k) puts("-1");
		else printf("%d\n",num[query(root[L[v]],root[R[v]],1,sz,k)-1]);
	}
}

int main(){
	input();
	disc();
	Kruskal();
	Ac();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值