kruskal重构树

一,定义

kruskal是求最小生成树的一种算法。最小生成树

但是这种结合并查集的特殊方法给了他许多特殊的性质。可以用于解决树上瓶颈边权之类的问题

结合这种算法而诞生的就是——kruskal重构树

二,建树思路及其性质

kruskal求最小生成树是将边权小的边先连起来。而重构树则是同理,他把当前最小边抽象为一个虚点(虚点的值为该边权值),然后把这条边对应的两个不同集合视为左右儿子,将集合与该虚点建边

 建树后我们很清晰地发现它有如下性质

  1. 是一颗二叉树
  2. 虚点有n-1个,且值随着树的建立不断变大(因为是从小边建到大边嘛)
  3. 父节点都是虚点,叶子节点都是原来图中的点
  4. 如果我们从图中任意x点到y点,那么他们路过的最大边权边就是他们的重构树lca虚点val值
  5. 换句话说:原图中x到y的所有简单路径中最大边权的最小值是val[lca(x,y)](因为所有路径中,每条边都最小的肯定是最小生成树的,那么最小中的最大边权也肯定是所有路径中最大边权中最小的)==最小生成树上两个点之间的简单路径上的最大值= Kruskal 重构树上两点之间的 LCA 的权值

例题一:P1967 [NOIP2013 提高组] 货车运输 

思路:如果我们建最大生成树,那么lca(x,y)就是所有路径能通过的最小边权的最大值

#include <bits/stdc++.h>
using namespace std;
#define ll               long long
#define endl             "\n"
#define int              long long
#define endll            endl<<endl
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
//---------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------//
//double 型memset最大127,最小128
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 2e5 + 10;
struct node
{
	int u,v,w;
	bool operator<(const node&k)const
	{
		return w>k.w;
	}
} b[N<<1];
vector<int>edge[N<<1];
int fa[N],val[N<<1],dep[N],pre[N][32];

int find(int x)//并查集
{
	if(fa[x]!=x)fa[x]=find(fa[x]);
	return fa[x];
}

void dfs(int u,int f)//遍历重构树建立深度与预处理lca
{
	dep[u]=dep[f]+1;
	pre[u][0]=f;
	for(int i=1; i<=20; ++i)pre[u][i]=pre[pre[u][i-1]][i-1];
	for(auto v:edge[u])if(v!=f)dfs(v,u);
}

int LCA(int u,int v)
{
	if(dep[u]<dep[v])swap(u,v);
	if(dep[u]>dep[v])
		{
			int k=log2(dep[u]-dep[v]);
			for(int i=k; i>=0; --i)if(dep[pre[u][i]]>=dep[v])u=pre[u][i];
		}
	if(u==v)return u;
	int k=log2(dep[u]);
	for(int i=k; i>=0; --i)if(pre[u][i]!=pre[v][i])u=pre[u][i],v=pre[v][i];
	return pre[u][0];
}

void mysolve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1; i<=2*n; ++i)fa[i]=i;
	for(int i=1; i<=m; ++i)cin>>b[i].u>>b[i].v>>b[i].w;
	sort(b+1,b+1+m);
	int cnt=n;
	for(int i=1; i<=m; ++i)//重构
		{
			int fu=find(b[i].u),fv=find(b[i].v);
			if(fu!=fv)
				{
					val[++cnt]=b[i].w;
					fa[fu]=fa[fv]=cnt;
					edge[cnt].push_back(fu),edge[cnt].push_back(fv);
				}
		}
	for(int i=cnt; i>n; --i)if(fa[i]==i)dfs(i,i);//可能是森林
	int q,x,y;
	cin>>q;
	while(q--)
		{
			cin>>x>>y;
			if(find(x)!=find(y))cout<<-1<<endl;
			else cout<<val[LCA(x,y)]<<endl;
		}

}
int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t=1;
	//cin >> t;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

例题二:P4768 [NOI2018] 归程 

思路:

  1. 首先,我们容易想到,每次询问,只需要开车开到能靠近家(1点)位置,剩下走路即可
  2. 我们询问可以有logq的复杂度
  3. 那么我们如果先用dijksta预处理每个点到家的最短距离,然后我们重构树不是每个虚点子代的点表示只要你这个虚点我能来(我们建最大生成树),那么你的子节点我就能到达,所以这个虚点可以存储子树集合到达家的最小值。
  4. 那么我们每次 每次询问v点,尽可能跳到最大的祖先,剩下不行距离就是该祖先点存储的子树中到达家的最小距离。(能通过虚点说明子树是可以任意通过的)
#include <bits/stdc++.h>
using namespace std;
#define ll               long long
#define endl             "\n"
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 1e6 + 10;

struct node//kruskal的海拔
{
	int u,v,w;
	bool operator<(const node&k)const
	{
		return w>k.w;
	}
} b[N];

struct node1//最短路
{
	int next,to,w;
} edge[N];
int fa[N],pre[N][32],val[N],dis[N],head[N],num,cnt;
int dep[N];
vector<int>eg[N];//重构树的边

void add(int u,int v,int w)
{
	edge[++num].next=head[u];
	edge[num].to=v;
	edge[num].w=w;
	head[u]=num;
}

void init(int n)
{
	for(int i=1; i<=2*n; ++i)fa[i]=i,fa[n+i]=n+i,eg[i].clear(),head[i]=0;//注意,重构树建立后点变成两遍,所以开空间要开两倍,初始化也要
	num=0,cnt=n;
}

int find(int x)
{
	if(fa[x]!=x)fa[x]=find(fa[x]);
	return fa[x];
}

void dijkstra(int n)
{
	for(int i=1; i<=2*n; ++i)dis[i]=INF;//2n个点,包含虚点,跑dij不会碰到虚点(都没出生呢),一开始虚点显然离终点无限远
	dis[1]=0;
	priority_queue<pii,vector<pii>,greater<pii>>q;
	q.push({dis[1],1});
	while(!q.empty())
		{
			int u=q.top().second;
			q.pop();
			for(int i=head[u]; i; i=edge[i].next)
				{
					int v=edge[i].to,w=edge[i].w;
					if(dis[v]>dis[u]+w)
						{
							dis[v]=dis[u]+w;
							q.push({dis[v],v});
						}
				}
		}
}

void kruskal(int n,int m)
{
	sort(b+1,b+1+m);
	cnt=n;
	for(int i=1; i<=m; ++i)
		{
			int fu=find(b[i].u),fv=find(b[i].v);
			if(fu!=fv)
				{
					val[++cnt]=b[i].w;
					fa[fu]=fa[fv]=cnt;
					eg[cnt].push_back(fu),eg[cnt].push_back(fv);
				}
		}
}

void dfs(int u,int f)
{
	dep[u]=dep[f]+1;
	pre[u][0]=f;
	for(int i=1; i<=20; ++i)pre[u][i]=pre[pre[u][i-1]][i-1];
	for(auto v:eg[u])if(v!=f)
			{
				dfs(v,u);
				dis[u]=min(dis[v],dis[u]);//存储虚点的子树的最小距离
			}
}

void mysolve()
{
	int n,m;
	cin>>n>>m;
	init(n);
	int x,y,d,w;
	for(int i=1; i<=m; ++i)
		{
			cin>>x>>y>>d>>w;
			add(x,y,d),add(y,x,d);
			b[i].u=x,b[i].v=y,b[i].w=w;
		}
	dijkstra(n);
	kruskal(n,m);
	dfs(cnt,0);
	int q,k,s;
	cin>>q>>k>>s;
	int v,p,lantans=0;
	while(q--)
		{
			cin>>v>>p;
			v=(v+k*lantans-1)%n+1;
			p=(p+k*lantans)%(s+1);
			int h=log2(dep[v]);
			for(int i=h; i>=0; --i)
				if(dep[pre[v][i]]&&val[pre[v][i]]>p)v=pre[v][i];
			cout<<(lantans=dis[v])<<endl;
		}
}

int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t=1;
	cin >> t;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值