Kruskal 重构树

学习自墨染空大佬博客
墨染空大佬总结的太好了,下面的流程部分基本复制大佬的总结。

Kruskal 重构树流程

  1. 将所有边按边权从小到大排序
  2. 顺序遍历每条边 ( u , v , w ) (u,v,w) (u,v,w),若 u , v u,v u,v已经联通跳过,否则建立一个新点 x x x,让 x x x 作为 p u p_u pu p v p_v pv 的父亲(即连 x ⇒ p u x⇒p_u xpu x ⇒ p v x⇒p_v xpv 的有向边),然后让 p u = p v = x p_u=p_v=x pu=pv=x。这个新点的点权是 w w w
    O ( m l o g m ) O(mlogm) O(mlogm)

重构树性质

  1. 原图中的点在重构树中一定是叶子节点,其余节点都代表了一条边的边权。
  2. 重构树中的点数是 2 n − 1 2n-1 2n1且以 2 n − 1 2n-1 2n1号点为根节点
  3. 如果边权按照从小到大排序建立重构树,重构树的点权是一个大根堆,反之小根堆
  4. 对于一个 x x x 和一个值 v v v。从 x x x 出发只经过 ≤ v ≤v v的边能到达的点集 = x = x =x 的祖先节点中深度最小的点权 ≤ v ≤v v 的点 z z z 的子树中的原来的点集。
  5. 原树两点间的的最大边权就是kruskal重构树上两点的LCA的权值。

注意:对于某些性质的证明请直接阅读墨染空大佬博客

对于Kruskal 重构树能够解决的问题:能够代替可持久化并查集并且时间复杂度更优,好像也就能解决这类问题

【NOI2018】归程

考虑离线:我们可以按照海拔从大到小进行询问,便询问边加边使用并查集维护每个连通块的最小值即可。
由于强制在线我们可以按照海拔从小到大建立可持久化并查集,然后对于每一个询问二分某个版本查询即可。

上述做法是并查集做法。
如果用Kruskal 重构树,只需要按照海拔从大到小建立Kruskal 重构树,每个询问找到海拔最小的祖先节点,然后查询子树最小权值即可。

Code1 Kruskal 重构树

// P4768 [NOI2018] 归程 https://www.luogu.com.cn/problem/P4768
#include<bits/stdc++.h>

using namespace std;

using pii=pair<int,int>;
constexpr int N(200005),M=(400005),INF(0x3f3f3f3f);
struct Edge
{
	int u,v,w;
	bool operator<(const Edge &o)const
	{
		return w>o.w;
	}
}E[M];
int h[N],e[M<<1],ne[M<<1],w[M<<1],idx;
void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;}

int d[N],st[N];
int n,m;
priority_queue<pii,vector<pii>,greater<pii>> q;
void dij()
{
    for(int i=1;i<=n;i++) d[i]=INF,st[i]=0;
    d[1]=0;
    q.push({0,1});
    while(q.size())
    {
    	int u=q.top().second;q.pop();
    	if(st[u]) continue;
    	st[u]=1;
    	for(int i=h[u];i!=-1;i=ne[i])
    	{
    		int v=e[i];
    		if(d[v]>d[u]+w[i])
    		{
    			d[v]=d[u]+w[i];
    			q.push({d[v],v});
    		}
    	}
    }
}
vector<int> G[N<<1];
int fa[N<<1];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int tot,kv[N<<1];
void Merge(int u,int v,int w)
{
	u=find(u),v=find(v);
	if(u==v) return;
	fa[u]=fa[v]=++tot;
	kv[tot]=w;
	G[tot].push_back(u);G[tot].push_back(v);
}

int Fa[N<<1][21],val[N<<1];
void dfs(int u)
{
	val[u]=u<=n?d[u]:INF;
	for(int v:G[u])
	{
		Fa[v][0]=u;
		for(int k=1;k<=18;k++) Fa[v][k]=Fa[Fa[v][k-1]][k-1];
		dfs(v);
		val[u]=min(val[u],val[v]);
	}
}
int main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

	int Tc;cin>>Tc;
	while(Tc--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++) h[i]=-1;idx=0;
		for(int i=1;i<=m;i++)
		{
			int u,v,l,a;cin>>u>>v>>l>>a;
			add(u,v,l);add(v,u,l);
			E[i]={u,v,a};
		}
		dij();
		sort(E+1,E+1+m);
		for(int i=1;i<=2*n-1;i++) fa[i]=i;tot=n;
		for(int i=1;i<=2*n-1;i++) G[i].clear();
		for(int i=1;i<=m;i++) Merge(E[i].u,E[i].v,E[i].w);
		dfs(tot);
		int Q,K,S;cin>>Q>>K>>S;
		int lastans=0;
		while(Q--)
		{
			int v,p;cin>>v>>p;
			v=(v+K*lastans-1)%n+1;
            p=(p+K*lastans)%(S+1);
            for(int k=18;k>=0;k--)
            	if(kv[Fa[v][k]]>p) v=Fa[v][k];
            lastans=val[v];
            cout<<lastans<<'\n';
		}
	}
	return 0;
}

Code2 可持久化并查集

// P4768 [NOI2018] 归程 https://www.luogu.com.cn/problem/P4768
#include<bits/stdc++.h>

using namespace std;

using pii=pair<int,int>;
constexpr int N(200005),M=(400005),INF(0x3f3f3f3f);
struct Edge
{
	int u,v,w;
	bool operator<(const Edge &o)const
	{
		return w<o.w;
	}
}E[M];
int h[N],e[M<<1],ne[M<<1],w[M<<1],idx;
void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;}

int d[N],vis[N];
int n,m;
priority_queue<pii,vector<pii>,greater<pii>> q;
void dij()
{
    for(int i=1;i<=n;i++) d[i]=INF,vis[i]=0;
    d[1]=0;
    q.push({0,1});
    while(q.size())
    {
    	int u=q.top().second;q.pop();
    	if(vis[u]) continue;
    	vis[u]=1;
    	for(int i=h[u];i!=-1;i=ne[i])
    	{
    		int v=e[i];
    		if(d[v]>d[u]+w[i])
    		{
    			d[v]=d[u]+w[i];
    			q.push({d[v],v});
    		}
    	}
    }
}

struct Array
{
	struct Segment
	{
	    int l,r,v;
	}t[N*4+M*19];
	int rt[M],cnt;
	void build(int &u,int l,int r,int c)
	{
	    t[u=++cnt]={0,0,0};
	    if(l==r) 
	    {
	    	if(c==1) t[u].v=l;
	    	else if(c==2) t[u].v=1;
	    	else if(c==3) t[u].v=d[l];
	    	return;
	    }
	    int mid=l+r>>1;
	    build(t[u].l,l,mid,c),build(t[u].r,mid+1,r,c);
	}
	void update(int &u,int pre,int l,int r,int pos,int v)
	{
	    t[u=++cnt]=t[pre];
	    if(l==r) return t[u].v=v,void();
	    int mid=l+r>>1;
	    if(pos<=mid) 
	        update(t[u].l,t[pre].l,l,mid,pos,v);
	    else
	        update(t[u].r,t[pre].r,mid+1,r,pos,v);
	}
	int query(int u,int l,int r,int pos)
	{
	    if(l==r) return t[u].v;
	    int mid=l+r>>1;
	    if(pos<=mid) 
	        return query(t[u].l,l,mid,pos);
	    else
	        return query(t[u].r,mid+1,r,pos);
	}
	void clear(){cnt=0;}
	int inline get(int u,int pos){
		return query(rt[u],1,n,pos);
	}
	void inline ins(int u,int pos,int v)
	{
		update(rt[u],rt[u],1,n,pos,v);
	}
}sz,fa,val;
int find(int u,int x)
{
	int f=fa.get(u,x);
	return x==f?x:find(u,f);
}
void inline merge(int v, int a, int b) {
	a=find(v,a),b=find(v,b);
	if(a==b)return;
	int sa=sz.get(v,a),sb=sz.get(v,b);
	if (sa>sb) swap(a,b),swap(sa,sb);
	int va=val.get(v,a),vb=val.get(v,b);
	fa.ins(v,a,b);sz.ins(v,b,sa+sb);
	val.ins(v,b,min(va, vb));
}

int b[M],tot;
void clear()
{
	sz.clear(),fa.clear(),val.clear();
	for(int i=1;i<=n;i++) h[i]=-1;idx=0;
	tot=0;
}
int main()
{
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

	int Tc;cin>>Tc;
	while(Tc--)
	{
		
		cin>>n>>m;clear();
		for(int i=1;i<=m;i++)
		{
			int u,v,l,a;cin>>u>>v>>l>>a;
			add(u,v,l);add(v,u,l);
			E[i]={u,v,a};
			b[i]=a;
		}
		dij();
		sort(E+1,E+1+m);
		sort(b+1,b+1+m);tot=unique(b+1,b+1+m)-b-1;b[++tot]=2e9;

		fa.build(fa.rt[tot],1,n,1);
		sz.build(sz.rt[tot],1,n,2);
		val.build(val.rt[tot],1,n,3);	
		for(int i=tot-1,j=m;i>=1;i--)
		{
			fa.rt[i]=fa.rt[i+1];
			sz.rt[i]=sz.rt[i+1];
			val.rt[i]=val.rt[i+1];
			while(j&&E[j].w>=b[i]) 
			{
				merge(i,E[j].u,E[j].v);
				j--;
			}
		}
		int Q,K,S;cin>>Q>>K>>S;
		int lastans=0;
		while(Q--)
		{
			int v,p;cin>>v>>p;
			v=(v+K*lastans-1)%n+1;
            p=(p+K*lastans)%(S+1);
            int id=upper_bound(b+1,b+1+tot,p)-b;
            lastans=val.get(id,find(id,v));
            cout<<lastans<<'\n';
		}
	}
	return 0;
}

要加哟哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值