kruskal重构树练习

洛谷 P4197 Peaks

题意:

n n n 个山峰,每一个山峰高 h i h_i hi ,有 m m m 条双向带权边将一些山峰连接起来,有 q q q 次询问,每次询问 ( v , x , k ) (v,x,k) (v,x,k) ,即从 v v v 山峰出发经过边权不超过 x x x 的边能到的点里的第 k k k 高的山峰。

思路:

考虑在线的解法,要求经过边权不超过 x x x 的边能到的点,所以就想到使用 k r u s k a l kruskal kruskal 重构树,那么每一个山峰就是叶子节点,使用 d f s dfs dfs 序,那么每一个点都有一个区间,而 k r u s k a l kruskal kruskal 重构树是一个大根堆,要求不超过 x x x 的边权,即找到深度的最小的点且点权小于等于 x x x ,那么在该点对应区间上求区间第 k k k 大即可

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w<t.w;
	}
};
vector<edge>e,E[N];
vector<int>v;
int in[N],out[N],mx;
int val[N],w[N],fa[N],f[N][22];
int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
int fi(int x){if(x==fa[x])return x;return fa[x]=fi(fa[x]);}
int n,m,q,tot,Time;
//zx_tree
struct zx_tree{
	int ls[N*50],rs[N*50],rt[N*50],tot=0,sum[N*50];
	inline void update(int &root,int pre,int l,int r,int pos){
		root=++tot;ls[root]=ls[pre],rs[root]=rs[pre],sum[root]=sum[pre]+1;
		if(l==r)return ;
		int mid=(l+r)/2;
		if(pos<=mid)update(ls[root],ls[pre],l,mid,pos);
		else update(rs[root],rs[pre],mid+1,r,pos);
	}
    inline int query(int root,int pre,int l,int r,int k){//区间第k小
        if(l==r)return l;
        int mid=(l+r)/2;int Sum=sum[ls[root]]-sum[ls[pre]];
        if(Sum>k)return query(ls[root],ls[pre],l,mid,k);
        else return query(rs[root],rs[pre],mid+1,r,k-Sum);
    }
}T;
void cl(int x){
	for(int i=1;(1<<i)<=n;i++){
		f[x][i]=f[f[x][i-1]][i-1];
	}
}
void dfs(int x){
	cl(x);
	in[x]=++Time;
	if(x<=n)T.update(T.rt[Time],T.rt[Time-1],1,mx,getid(val[x]));//利用时间戳来建树
	else T.rt[Time]=T.rt[Time-1];
	for(edge now:E[x]){
		int v=now.to;
		dfs(v);
	}
	out[x]=Time;
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);v.push_back(val[i]);
	}
	for(int i=1;i<=2*n;i++)fa[i]=i;
	sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());mx=v.size();
	for(int i=1,u,v,w;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		e.push_back(edge(u,v,w));
	}
	sort(e.begin(),e.end());tot=n;
	for(edge now:e){
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		++tot;
		w[tot]=now.w;f[f1][0]=f[f2][0]=tot;
		fa[f1]=fa[f2]=tot;
		E[tot].push_back(edge(0,f1,0));E[tot].push_back(edge(0,f2,0));
		if(tot==2*n-1)break;
	}
	for(int i=1;i<=tot;i++)if(!in[i])dfs(fi(i));
	while(q--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		for(int i=18;~i;i--){
			if(f[a][i]&&w[f[a][i]]<=b)a=f[a][i];
		}
		int L=T.rt[in[a]-1],R=T.rt[out[a]];
		int Max=T.sum[R]-T.sum[L];
		if(Max<c){
			puts("-1");continue;
		}
		int pos=T.query(R,L,1,mx,Max-c);//第k大转化为第k小
		printf("%d\n",v[pos-1]);
	}
}

Comet OJ - Contest #11 D.isaster

题意:

对一张 n n n 个点 m m m 条边点带权的无向连通图进行以下两种操作:
1.修改点 x x x 的点权。
2.询问从点 x x x 出发只经过编号不大于 y y y 的点能到达的所有点的点权之积取模 998244353 998244353 998244353

思路:

和上一题类似,只要将kruskal重构树求出来,在利用线段树维护每一个点的点权积即可,支持单点修改。
(不知道为什么用c++就一直RE,改了两个小时,用c++17就过了)

代码:

#include <bits/stdc++.h>
#define ll long long
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e6+10;
const int mod=998244353;
int n,m,q,op,x,y;
int val[N],fa[N],f[N][22],arr[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w<t.w;
	}
};
vector<edge>e,E[N];//原边和新构的二叉树,一个大根堆
void kurskal(){
	for(edge now:e){//
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		if(f1<f2)swap(f1,f2);
		fa[f2]=f1;
		E[f1].push_back(edge(0,f2,0));//加入新边
	}
}
int Time,in[N],out[N],id[N];
void dfs(int x,int Fa){
	f[x][0]=Fa;
	for(int i=1;i<20;i++)f[x][i]=f[f[x][i-1]][i-1];
	in[x]=++Time;id[Time]=x;
	for(edge now:E[x]){
		int v=now.to;
		dfs(v,x);
	}
	out[x]=Time;
}
//
struct node{
	int l,r;
	ll sum;
}T[N*4];
void up(int x){
	T[x].sum=(T[ls].sum*T[rs].sum)%mod;
}
void built(int x,int l,int r){
	T[x].l=l;T[x].r=r;
	if(l==r){
		T[x].sum=val[id[l]];return ;
	}
	int mid=(l+r)/2;
	built(ls,l,mid);built(rs,mid+1,r);
	up(x);
}
void add(int x,int pos,int val){
	if(T[x].l==T[x].r){
		T[x].sum=val%mod;return ;
	}
	int mid=(T[x].l+T[x].r)/2;
	if(pos<=mid)add(ls,pos,val);
	else add(rs,pos,val);
	up(x);
}
ll query(int x,int LL,int RR){
	if(T[x].l>=LL&&T[x].r<=RR){
		return T[x].sum%mod;
	}
	int mid=(T[x].l+T[x].r)/2;
	ll ans=1;
	if(LL<=mid)ans=(ans*query(ls,LL,RR))%mod;
	if(RR>mid)ans=(ans*query(rs,LL,RR))%mod;
	return ans;
}
//

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=n;i++)scanf("%d",&val[i]),val[i]%=mod;
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		//if(u==v)continue;
		e.push_back(edge(u,v,max(u,v)));
	}
	sort(e.begin(),e.end());
	kurskal();
	dfs(n,0);
	built(1,1,Time);
	while(q--){
		scanf("%d%d%d",&op,&x,&y);
		if(op==1){
			if(x>y){
				puts("0");continue;
			}
			for(int i=19;~i;i--){
				if(f[x][i]&&f[x][i]<=y)x=f[x][i];
			}
			int L=in[x],R=out[x];
			printf("%lld\n",query(1,L,R));
		}else{
			add(1,in[x],y);
		}
	}
}

洛谷 P4768 [NOI2018]归程

题意:

给一个无向图,每一条边有一个长度和海拔,结点 1 1 1 是终点,多次询问,每次询问从 v v v 点出发要到终点,给定一个 k k k 值,对于经过的每一条边,如果海拔大于等于 k k k 可以使用车,如果小于则只能步行,在每一个结点可以随时下车步行,但不能再次上车,问到终点的最小步行长度。

思路:

由于是大于等于p,故我们在建重构树的时候使用最大生成树的建法,使其成为一个小根堆,那么对于每次询问,和上面一样我们倍增找到一个结点,可以知道该结点形成的子树都可以使用车,那么步行的最短距离就是子树的点中与终点的最短距离,可以预处理出来。

代码:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=4e5+10;
int n,m,T,ans,q,k,s,v,p,dis[N],w[N],v0,p0;
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w>t.w;
	}
};
int f[N][22],fa[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
vector<edge>e,E[N],E1[N];
int tot;
void kurskal(){
	for(edge now:e){
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		++tot;
		w[tot]=now.w;f[f1][0]=f[f2][0]=tot;
		fa[f1]=fa[f2]=tot;
		E[tot].push_back(edge(0,f1,0));E[tot].push_back(edge(0,f2,0));//加入新边
		if(tot==2*n-1)break;
	}
}
void init(){
	tot=n;e.clear();ans=0;for(int i=1;i<=n;i++)w[i]=inf;
	for(int i=1;i<=2*n;i++)fa[i]=i,E[i].clear(),E1[i].clear();
	for(int i=1;i<=2*n;i++)
	for(int j=0;j<=20;j++)f[i][j]=0;

}
void change(){
	v=(v0+k*ans-1)%n+1;
	p=(p0+k*ans)%(s+1);
}
bool vis[N];
int val[N];
void dfs(int x){
	vis[x]=1;
	for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
	val[x]=dis[x];
	//cout<<"edge:"<<x<<" "<<val[x]<<endl;
	for(edge now:E[x]){
		int v=now.to;
		//cout<<"next:"<<v<<endl;
		dfs(v);
		val[x]=min(val[x],val[v]);
	}
}
void dij(){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	priority_queue<pair<int,int> >q;
	q.push(make_pair(0,1));dis[1]=0;
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(vis[now])continue;
		vis[now]=1;
		for(edge nnow:E1[now]){
			int v=nnow.to;
			if(dis[v]>dis[now]+nnow.w){
				dis[v]=dis[now]+nnow.w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
	//for(int i=1;i<=2*n;i++)cout<<dis[i]<<endl;
}
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1,u,v,x,y;i<=m;i++){
			scanf("%d%d%d%d",&u,&v,&x,&y);
			e.push_back(edge(u,v,y));
			E1[u].push_back(edge(0,v,x));
			E1[v].push_back(edge(0,u,x));
		}sort(e.begin(),e.end());kurskal();dij();
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=tot;i++)if(!vis[i])dfs(fi(i));
		//for(int i=1;i<=tot;i++)cout<<val[i]<<endl;
		scanf("%d%d%d",&q,&k,&s);
		while(q--){
			scanf("%d%d",&v0,&p0);
			change();
			for(int i=19;~i;i--){
				if(f[v][i]&&w[f[v][i]]>p)v=f[v][i];
			}
			//cout<<v<<endl;
			printf("%d\n",val[v]);ans=val[v];
		}

	}
}

2016 ACM-ICPC CHINA-Final G. Pandaria

题意:

给一个树,每条边有边权,每个点有颜色,多次询问,强制在线,每次询问从 v v v 点出发,经过边权不超过 q q q 的边能到的点里,出现最多次的颜色。

思路:

学习了 k r u s k a l kruskal kruskal 重构树后这题就比较简单了,首先利用重构树得到能到达的点集,问题就是如何维护出现最多次的颜色,我们可以先不重构树,而是对每一个点建立一个权值线段树,在重构的时候合并线段树,那么合并后的结点信息就是答案,在回答时先倍增到目标结点,然后利用线段树查询。(细节较多,注意初始化)

代码:

#include <bits/stdc++.h>
#define ls e[x].l
#define rs e[x].r
using namespace std;
const int N=2e5+10;
int n,T,m,q,c[N];
struct node{//权值线段树
	int l,r,sum,id;
}e[N*50];
void up(int x){
	e[x].sum=max(e[ls].sum,e[rs].sum);
	if(e[ls].sum==e[x].sum)e[x].id=e[ls].id;//取较小的
	else e[x].id=e[rs].id;
}
int cnt,tot,rt[N*50];//开50倍
void update(int &x,int l,int r,int pos){
	if(!x)x=++cnt;
	if(l==r){
		e[x].sum++;e[x].id=l;return ;
	}
	int mid=(l+r)/2;
	if(pos<=mid)update(ls,l,mid,pos);
	else update(rs,mid+1,r,pos);
	up(x);
}
int merge(int x,int y,int l,int r){
	if(!x||!y)return x+y;
	if(l==r){
		e[x].sum+=e[y].sum;
		e[x].id=l;return x;
	}
	int mid=(l+r)/2;
	ls=merge(e[x].l,e[y].l,l,mid);
	rs=merge(e[x].r,e[y].r,mid+1,r);
	up(x);
	return x;
}
int ans[N];
int fa[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
struct edge{
	int from,to,w;
	edge(int a=0,int b=0,int c=0){from=a,to=b,w=c;}
	bool operator < (const edge &t)const{
		return w<t.w;
	}
}E[N*2];
bool vis[N];
int son[N][2],f[N][22],w[N];
void kruskal(){
	sort(E+1,E+m+1);
	for(int i=1;i<=m;i++){
		int f1=fi(E[i].from),f2=fi(E[i].to);
		if(f1==f2)continue;
		++tot;
		w[tot]=E[i].w;rt[tot]=merge(rt[f1],rt[f2],1,n);//将合并的结点给新节点赋值
		son[tot][0]=f1,son[tot][1]=f2;
		f[f1][0]=f[f2][0]=tot;fa[f2]=fa[f1]=tot;
		ans[tot]=e[rt[tot]].id;//存储答案
	}
}
void dfs(int u){
	vis[u]=1;
	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];//倍增
	if(son[u][0])dfs(son[u][0]);
	if(son[u][1])dfs(son[u][1]);
}
int Ans;
void init(){//
	Ans=0;
	for(int i=1;i<=2*n;i++)rt[i]=0,vis[i]=0;
	for(int i=1;i<=cnt;i++)e[i].l=e[i].r=e[i].sum=e[i].id=0;
	for(int i=1;i<=2*n;i++)fa[i]=i,son[i][0]=son[i][1]=0;//很关键,否则TLE
	for(int i=1;i<=2*n;i++)
	for(int j=1;j<=20;j++)f[i][j]=0;
	tot=n;cnt=0;
}
int main()
{
	scanf("%d",&T);int ca=1;
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		printf("Case #%d:\n",ca++);
		for(int i=1;i<=n;i++)scanf("%d",&c[i]),update(rt[i],1,n,c[i]);
		for(int i=1,u,v,w;i<=m;i++){
			scanf("%d%d%d",&u,&v,&w);E[i]=edge(u,v,w);
		}
		for(int i=1;i<=n;i++)ans[i]=c[i];
		kruskal();for(int i=1;i<=tot;i++)if(!vis[i])dfs(fi(i));
		scanf("%d",&q);
		while(q--){
			int x,W;
			scanf("%d%d",&x,&W);
			x^=Ans;W^=Ans;
			for(int i=19;~i;i--){
				if(f[x][i]&&w[f[x][i]]<=W)x=f[x][i];
			}
			//cout<<x<<endl;
			printf("%d\n",ans[x]);Ans=ans[x];
		}
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值