bzoj-3545 Peaks

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个n个点m条边的无向图,点和边都有权值;

q次询问从某个点出发,经过不超过x的权值的边能到达的第k大点权值;

1<=n<=100000,1<=m,q<=500000;

权值<=10^9;


题解:

题意略鬼畜。。。

首先能看出来走的边一定是在最小生成树上,然后这并没有什么卵用;

不过至少提供了一点思路,剩下思路就是将询问离线了;

离线之后可以做到一次遍历边表,顺便的求出所有的答案值;

对于每次的询问,答案就是那个点所在连通块的k大值;

这里我用Treap的启发式合并实现;

似乎是第一次写启发式合并的题(并查集不算= =),不过实现似乎挺诡异了点。。

时间复杂度O(nlog^2n);


代码:


#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
using namespace std;
struct edge
{
	int x,y,v;
	friend bool operator <(edge a,edge b)
	{
		return a.v<b.v;
	}
}E[N*5];
struct Query
{
	int x,v,k,no;
	friend bool operator <(Query a,Query b)
	{
		return a.v<b.v;
	}
}Q[N*5];
int ans[N*5];
int ls[N],rs[N],val[N],fa[N],size[N],rnd[N];
void Pushup(int x)
{
	size[x]=size[ls[x]]+size[rs[x]]+1;
}
int find(int x,int k)
{
	if(k<=0||k>size[x])
		return -1;
	if(size[ls[x]]>=k)
		return find(ls[x],k);
	else if(size[ls[x]]+1==k)
		return val[x];
	else
		return find(rs[x],k-size[ls[x]]-1);
}
void lturn(int &x)
{
	int p=rs[x];
	rs[x]=ls[p];
	ls[p]=x;
	fa[rs[x]]=x;
	fa[p]=fa[x];
	fa[x]=p;
	size[p]=size[x];
	Pushup(x);
	x=p;
}
void rturn(int &x)
{
	int p=ls[x];
	ls[x]=rs[p];
	rs[p]=x;
	fa[ls[x]]=x;
	fa[p]=fa[x];
	fa[x]=p;
	size[p]=size[x];
	Pushup(x);
	x=p;
}
void Insert(int &x,int t)
{
	if(!x)
	{
		x=t;
		return ;
	}
	size[x]++;
	if(val[t]>val[x])
	{
		Insert(ls[x],t);
		fa[ls[x]]=x;
		if(rnd[ls[x]]>rnd[x])
			rturn(x);
	}
	else
	{
		Insert(rs[x],t);
		fa[rs[x]]=x;
		if(rnd[rs[x]]>rnd[x])
			lturn(x);
	}	
}
void Link(int &x,int &y)
{
	if(!x||!y)	return ;
	fa[ls[x]]=fa[rs[x]]=0;
	Link(ls[x],y);
	Link(rs[x],y);
	ls[x]=rs[x]=0;
	size[x]=1;
	Insert(y,x);
}
int query(int x,int k)
{
	while(fa[x])	x=fa[x];
	return find(x,k);
}
int main()
{
	srand(140142);
	int n,m,q,i,j,k,x,y,fx,fy;
	scanf("%d%d%d",&n,&m,&q);
	for(i=1;i<=n;i++)
	{
		scanf("%d",val+i);
		size[i]=1;
		rnd[i]=rand();
	}
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].v);
	}
	for(i=1;i<=q;i++)
	{
		scanf("%d%d%d",&Q[i].x,&Q[i].v,&Q[i].k);
		Q[i].no=i;
	}
	sort(E+1,E+m+1);
	sort(Q+1,Q+q+1);
	for(i=1,j=0;i<=q;i++)
	{
		while(j<m&&E[j+1].v<=Q[i].v)
		{
			j++;
			while(fa[E[j].x])	E[j].x=fa[E[j].x];
			while(fa[E[j].y])	E[j].y=fa[E[j].y];
			if(E[j].x!=E[j].y)
			{
				if(size[E[j].x]>size[E[j].y])
					swap(E[j].x,E[j].y);
				Link(E[j].x,E[j].y);
			}
		}
		ans[Q[i].no]=query(Q[i].x,Q[i].k);
	}
	for(i=1;i<=q;i++)
		printf("%d\n",ans[i]);
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值