Kruskal重构树

【算法简介】

算法流程和Kruskal生成树类似,把边排序后,依据并查集加边即可,两个点认一个新的节点父亲,这个点的点权就是当前边权

所以我们最后得到了一个有2n-1个节点的带点权的树

	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n*2;i++) fa[i]=i;
	cnt=n;
	for(int i=1;i<=m;i++)
	{
		int fx=find(e[i].u),fy=find(e[i].v);
		if(fx!=fy)
		{
			fa[fx]=fa[fy]=++cnt;
			ew[cnt]=e[i].val;
			add(cnt,fx); add(cnt,fy);
			in[fx]++; in[fy]++;
		}
	}

性质:

1、u,v经过的最大值,就是重构树上lca的值

如果从小到大加边,可以考虑越加边图的连通性越强,也就是说u->v的最大值,就是刚好加了某个点,使得u,v联通,这个点就是刚刚加入的u,v的lca

2、重构树中代表原树中的点的节点全是叶子节点,其余节点都代表了一条边的边权。

【例题】P4197 Peaks

【题意】

有点权也有边权的图中一点v开始走不超过x的边权,能够走到的所有节点的点权中第k大的值

【分析】

建出Kruskal重构树,问题就转化为了求子树中的第k大问题,就能想到主席树了

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
const int maxm=5e5+5;
const int inf=0x3f3f3f3f;
int n,m,q,root[maxn];
int h[maxn],in[maxn],fa[maxn];
struct edge
{
	int u,v,val;
}e[maxm];
bool cmp(edge a,edge b)
{
	return a.val<b.val;
}
int cnt,ew[maxn];
int find(int x)
{
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
int head[maxn],tot;
struct edgee
{
	int to,nxt;
}G[maxn<<1];
void add(int x,int y)
{
	G[++tot].to=y; G[tot].nxt=head[x]; head[x]=tot;
}
int intime[maxn],f[maxn][20],siz[maxn],outime[maxn],pos[maxn],dfstime;
void dfs(int x)
{
	intime[x]=++dfstime; pos[dfstime]=x;
	//siz[x]=1;
	for(int i=head[x];i;i=G[i].nxt)
	{
		int to=G[i].to;
		if(to==fa[x]) continue;
		f[to][0]=x;
		dfs(to);
		siz[x]+=siz[to];
	}
	if(!siz[x]) siz[x]=1;
	outime[x]=dfstime;
}
void lca_init()
{
	for(int k=1;k<=19;k++)
		for(int i=1;i<=n*2;i++)
			f[i][k]=f[f[i][k-1]][k-1];
}
struct segtree
{
	int l,r,sum;
}tr[maxn<<5];
int etot;
void update(int &now,int L,int R,int pos,int val)
{
	tr[++etot]=tr[now]; tr[etot].sum+=val;
	now=etot;
	if(L==R) return;
	int mid=L+R>>1;
	if(pos<=mid) update(tr[now].l,L,mid,pos,val);
	if(mid<pos) update(tr[now].r,mid+1,R,pos,val);
}
int calc(int rx,int ry,int L,int R,int k)
{
	if(L==R) return L;
	int sum=tr[tr[ry].r].sum-tr[tr[rx].r].sum;
	int mid=L+R>>1;
	if(sum>=k) return calc(tr[rx].r,tr[ry].r,mid+1,R,k);
	else return calc(tr[rx].l,tr[ry].l,L,mid,k-sum);
}
int query(int v,int x,int k)
{
	for(int i=18;i>=0;i--)  if(f[v][i] && ew[f[v][i]]<=x) v=f[v][i];
	if(siz[v]<k) return -1;
	int l=intime[v],r=outime[v];
	return calc(root[l-1],root[r],0,inf,k);
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		scanf("%d",&h[i]);
	int v,x,k;
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].val);
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n*2;i++) fa[i]=i;
	cnt=n;
	for(int i=1;i<=m;i++)
	{
		int fx=find(e[i].u),fy=find(e[i].v);
		if(fx!=fy)
		{
			fa[fx]=fa[fy]=++cnt;
			ew[cnt]=e[i].val;
			add(cnt,fx); add(cnt,fy);
			in[fx]++; in[fy]++;
		}
	}
	for(int i=1;i<=cnt;i++)	if(!in[i])	dfs(i); 
	lca_init();
	for(int i=1;i<=cnt;i++)
	{
		root[i]=root[i-1];
		if(pos[i]<=n) update(root[i],0,inf,h[pos[i]],1);
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&v,&x,&k);
		printf("%d\n",query(v,x,k));
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值