Kruskal重构树

kruskal重构树

KruskalKruskalKruskal重构树是一种在图论中用于解决特定问题的数据结构,它基于经典的KruskalKruskalKruskal最小生成树算法构建。它并非直接用于求解最小生成树,而是通过重构原图的边,将边权信息"上移"到新生成的节点上,从而将路径上的边权最值问题转化为树上路径的节点最值问题,并能方便地处理连通性和LCALCALCA 相关的问题

所谓KruskalKruskalKruskal重构树就是把原图重构为一颗二叉树,树上的每一个叶子节点都是原图中的点

重构过程:将所有边权从小到大排序,从小到大选择边,如果两个点不在同一个集合中就将这两个点的祖先连到一个虚点上,这个虚点的点权就等于这条边的边权

神奇的性质:

  1. 每条边都会新生成一个虚点,因此会增加n−1n-1n−1个虚点

  2. 原图有几个连通块就会重构出几棵树,若p[i]=ip[i]=ip[i]=i则说明iii是一棵树的树根

  3. 节点越往上代表边权越大,其连通性也越大,从任何一个点往根上引一条路径,这条路径经过的点的点权单调不降(最大生成树单调不升)

  4. 任意两点之间的路径的最大边权就是他们lcalcalca的点权,uuu和 vvv的LCALCALCA节点的点权,就是连接uuu和vvv所在连通分量时所用的那条边的权值。

应用场景:

  1. 最小瓶颈路问题: 求两点间路径上最大边权的最小值。使用边权升序构建的重构树,答案就是LCALCALCA的点权
  2. 最大瓶颈路问题: 求两点间路径上最小边权的最大值。使用边权降序构建的重构树,答案就是LCALCALCA的点权。

P2245 星际迷航

题意

一张有权无向图,图中可能有若干联通部分,给出qqq个询问,每次询问xxx到yyy的路径中最大边权最小值

代码
cpp 复制代码
const int N=2e5+10;

struct edge{
	int u,v,w;
	bool operator < (const edge&t)const
	{
		return w<t.w;
	}
};

int p[N];
vector<int>e[N];
int dep[N];
int fa[N][21];


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

void dfs(int u,int father)
{
	dep[u]=dep[father]+1;
	fa[u][0]=father;
	for(int i=1;(1<<i)<=dep[u];i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	
	for(auto ed:e[u])
	{
		if(ed==father) continue;
		dfs(ed,u);
	}
}

int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	
	for(int i=19;i>=0;i--)
	{
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
		if(u==v) return v;
	}
		
	for(int i=19;i>=0;i--)
	{
		if(fa[u][i]!=fa[v][i])
		{
			u=fa[u][i];
			v=fa[v][i];
		}
	}
	return fa[u][0];
}


void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=2*n;i++) p[i]=i;
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e1[i]={a,b,c};
	}
	
	sort(all(e1));
	vector<int>a(2*n+1);
	int cnt=n;
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			a[cnt]=w;
			p[x]=cnt;
			p[y]=cnt;
		}
	}
	
	for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);//树根
	
	int q;cin>>q;
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		if(find(x)!=find(y)) cout<<"impossible"<<endl;
		else cout<<a[lca(x,y)]<<endl;
	}
	
}

P1967 货车运输

题目描述

A 国有 nnn 座城市,编号从 111 到 nnn,城市之间有 mmm 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 qqq 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

第一行有两个用一个空格隔开的整数 n,mn,mn,m,表示 A 国有 nnn 座城市和 mmm 条道路。

接下来 mmm 行每行三个整数 x,y,zx, y, zx,y,z,每两个整数之间用一个空格隔开,表示从 xxx 号城市到 yyy 号城市有一条限重为 zzz 的道路。

注意:x≠yx \neq yx=y,两座城市之间可能有多条道路。

接下来一行有一个整数 qqq,表示有 qqq 辆货车需要运货。

接下来 qqq 行,每行两个整数 x,yx,yx,y,之间用一个空格隔开,表示一辆货车需要从 xxx 城市运输货物到 yyy 城市,保证 x≠yx \neq yx=y。

共有 qqq 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。

如果货车不能到达目的地,输出 −1-1−1。

思路

等价于求两点之间可能路径的最大瓶颈,重构最大生成树即可

代码
cpp 复制代码
const int N=2e4+10;

struct edge{
	int u,v,w;
	bool operator < (const edge&t)const
	{
		return w>t.w;
	}
};
vector<int>e[N];
int p[N];
int dep[N];
int fa[N][20];

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

void dfs(int u,int father)
{
	dep[u]=dep[father]+1;
	fa[u][0]=father;
	for(int i=1;(1<<i)<=dep[u];i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	
	for(auto ed:e[u])
	{
		if(ed==father) continue;
		dfs(ed,u);
	}
}

int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	
	for(int i=19;i>=0;i--)
	{
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
		if(u==v) return v;
	}
		
	for(int i=19;i>=0;i--)
	{
		if(fa[u][i]!=fa[v][i])
		{
			u=fa[u][i];
			v=fa[v][i];
		}
	}
	return fa[u][0];
}
void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=2*n;i++) p[i]=i;
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e1[i]={a,b,c};
	}
	
	int cnt=n;
	sort(all(e1));
	vector<int>a(2*n+1);
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			a[cnt]=w;
			p[x]=cnt;
			p[y]=cnt;
			
		}
	}
	
	for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
	int q;cin>>q;
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		if(find(x)!=find(y)) cout<<-1<<endl;
		else cout<<a[lca(x,y)]<<endl;
	}
	
}

P9638 youyou的军训

题意

一张nnn点mmm边的无向有权图,有三种操作

  • 1 x,删去所有边权 ≤x≤x≤x的边,并恢复之前因为该操作而断开的所有边。
  • 2 x,查询节点xxx能到达的包括自己的点的数量。
  • 3 y,将第xxx条边的边权修改为yyy
    保证所有边的相对边权排名不变
思路

操作111等价于只能走边权>x>x>x的边,很容易想到瓶颈路,而且保证边的相对排名不会改变,也就是说我们不用多次重构,保证了重构树做法的可行性

具体地,我们按最大生成树来重构,对于修改边权操作我们只需要建立一个边对虚点的映射,每次只更改虚点的点权即可,由于修改操作会在操作1之后统一修改,我们将所有的操作333存起来直到操作111出现后统一修改即可,对于查询,本质上询问的是点xxx只走边权大于limitlimitlimit的边,可以连通的节点数量,我们可以预处理出每个虚点作为根节点时的叶子节点数量(叶节点即可达节点),每次询问倍增跳到最大的合法虚点即可lognlognlogn完成每次查询

代码
cpp 复制代码
const int N=4e6+10;

struct edge{
	int u,v,w,id;
	bool operator < (const edge &t) const
	{
		return w>t.w;
	}
};
vector<int>e[N];
int p[N],dep[N];
int fa[N][20];
int siz[N];
int n;

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

void dfs(int u,int father)
{
	fa[u][0]=father;
	dep[u]=dep[father]+1;
	if(u<=n) siz[u]=1;
	for(int i=1;(1<<i)<=dep[u];i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	
	for(auto ed:e[u])
	{
		if(ed==father) continue;
		dfs(ed,u);
		siz[u]+=siz[ed];
	}
}



void solve()
{
	int m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=2*n;i++) p[i]=i;
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e1[i]={a,b,c,i};
	}
	
	sort(all(e1));
	vector<int>w1(2*n+1);
	vector<int>topoint(2*n+1);//边对虚点的映射
	int cnt=n;
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w,id]=e1[i];
		int x=find(u);
		int y=find(v);
		
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			p[x]=cnt;
			p[y]=cnt;
			w1[cnt]=w;
			topoint[id]=cnt;
		}
	}
	
	for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
	vector<PII>change;
	int limit=0;
	
	auto query=[&](int x,int limit)
	{
		for(int i=19;i>=0;i--)
		{
			if(fa[x][i] && w1[fa[x][i]]>=limit) x=fa[x][i];
		}
		return siz[x];
	};
	
	while(q--)
	{
		int op;cin>>op;
		if(op==1)
		{
			for(auto [x,y]:change) w1[topoint[x]]=y;
			change.clear();
			cin>>limit;
		}
		else if(op==2)
		{
			int x;cin>>x;
			cout<<query(x,limit)<<endl;
		}
		else
		{
			int x,y;cin>>x>>y;
			if(topoint[x]) change.pb({x,y});
		}
	}
}

agc_002D

题意

一张nnn点mmm边的无向有权图,每条边的权值是其编号,给出qqq个询问,每次询问从xxx和yyy出发访问不同的节点恰zzz个,使得经过的边编号最大值最小,求这个最小值

思路

把编号作为边权,按最小生成树重构原图,求最大值最小显然可以二分,每次checkcheckcheck只需要判断从xxx和yyy尽可能往上跳到边权≤mid\leq mid≤mid的虚点,判断这两个虚点最多能访问多少不同节点即可,这部分可以预处理之后倍增lognlognlogn实现,有个细节是若xxx和yyy最后重合了,就只返回siz\[x\]

代码
cpp 复制代码
const int N=2e5+10;

struct edge{
	int u,v,w;
	bool operator < (const edge&t)const
	{
		return w<t.w;
	}
};

int p[N];
int fa[N][20];
int siz[N];
vector<int>e[N];

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

void solve()
{
	int n,m;
	cin>>n>>m;
	
	for(int i=1;i<=2*n;i++) p[i]=i;
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		e1[i]={a,b,i};
	}
	
	sort(all(e1));
	
	int cnt=n;
	vector<int>w1(2*n+1);
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			p[x]=cnt;
			p[y]=cnt;
			w1[cnt]=w;
		}
	}
	
	auto dfs=[&](auto &&dfs,int u,int father)->void
	{
		fa[u][0]=father;
		if(u<=n) siz[u]=1;
		
		for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
		for(auto ed:e[u])
		{
			if(ed==father) continue;
			dfs(dfs,ed,u);
			siz[u]+=siz[ed];
		}
	};
	
	for(int i=1;i<=cnt;i++) if(i==p[i]) dfs(dfs,i,0);
	
	
	
	auto check=[&](int x,int y,int limit)
	{
		for(int i=19;i>=0;i--)
		{
			if(fa[x][i] && w1[fa[x][i]]<=limit) x=fa[x][i];
			if(fa[y][i] && w1[fa[y][i]]<=limit) y=fa[y][i]; 
		}
		if(x==y) return siz[x];
		else return siz[x]+siz[y];
	};
	
	int q;cin>>q;
	while(q--)
	{
		int x,y,z;
		cin>>x>>y>>z;
		int l=0,r=m+1;
		while(l+1<r)
		{
			int mid=(l+r)>>1;
			//cout<<check(x,y,mid)<<endl;
			if(check(x,y,mid)>=z) r=mid;
			else l=mid;
		}
		cout<<r<<endl;
	}
	
}

P4768 归程

题目描述

本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。

魔力之都可以抽象成一个 nnn 个节点、mmm 条边的无向连通图(节点的编号从 111 至 nnn)。我们依次用 l,al,al,a 描述一条边的长度、海拔

作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边 。我们用水位线 来描述降雨的程度,它的意义是:所有海拔不超过 水位线的边都是有积水的。

Yazid 是一名来自魔力之都的 OIer,刚参加完 ION2018 的他将踏上归程,回到他温暖的家。Yazid 的家恰好在魔力之都的 111 号节点。对于接下来 QQQ 天,每一天 Yazid 都会告诉你他的出发点 vvv ,以及当天的水位线 ppp。

每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。

需要特殊说明的是,第二天车会被重置,这意味着:

  • 车会在新的出发点被准备好。
  • Yazid 不能利用之前在某处停放的车。

Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。

本题的部分测试点将强制在线,具体细节请见【输入格式】和【子任务】。

单个测试点中包含多组数据。输入的第一行为一个非负整数 TTT,表示数据的组数。

接下来依次描述每组数据,对于每组数据:

第一行 222 个非负整数 n,mn,mn,m,分别表示节点数、边数。

接下来 mmm 行,每行 444 个正整数 u,v,l,au, v, l, au,v,l,a,描述一条连接节点 u,vu, vu,v 的、长度为 lll、海拔为 aaa 的边。

在这里,我们保证 1≤u,v≤n1 \leq u,v \leq n1≤u,v≤n。

接下来一行 333 个非负数 Q,K,SQ, K, SQ,K,S ,其中 QQQ 表示总天数,K∈0,1K \in {0,1}K∈0,1 是一个会在下面被用到的系数,SSS 表示的是可能的最高水位线。

接下来 QQQ 行依次描述每天的状况。每行 222 个整数 v0,p0v_0, p_0v0,p0 描述一天:

  • 这一天的出发节点为 v=(v0+K×lastans−1) mod n+1v = (v_0 + K \times \mathrm{lastans} - 1) \bmod n + 1v=(v0+K×lastans−1)modn+1。
  • 这一天的水位线为 p=(p0+K×lastans) mod (S+1)p = (p_0 + K \times \mathrm{lastans}) \bmod (S + 1)p=(p0+K×lastans)mod(S+1)。

其中 lastans\mathrm{lastans}lastans 表示上一天的答案(最小步行总路程)。特别地,我们规定第 111 天时 lastans=0\mathrm{lastans} = 0lastans=0。

在这里,我们保证 1≤v0≤n1 \leq v_0 \leq n1≤v0≤n,0≤p0≤S0 \leq p_0 \leq S0≤p0≤S。

对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。

所有测试点均保证 T≤3T\leq 3T≤3,所有测试点中的所有数据均满足如下限制:

  • n≤2×105n\leq 2\times 10^5n≤2×105,m≤4×105m\leq 4\times 10^5m≤4×105,Q≤4×105Q\leq 4\times 10^5Q≤4×105,K∈{0,1}K\in\left\{0,1\right\}K∈{0,1},1≤S≤1091\leq S\leq 10^91≤S≤109。
  • 对于所有边:l≤104l\leq 10^4l≤104,a≤109a\leq 10^9a≤109。
  • 任意两点之间都直接或间接通过边相连。
思路

大概意思就是可以坐车走边权>y>y>y的边,这个代价是免费的,然后选择一个点下车,步行到111号点,求步行的最小代价,首先预处理出所有点到111号点的最短路,因为只能走>y>y>y的边,我们肯定希望尽可能的坐车走免费的边,那么我们按最大生成树重构原图,看每次坐车能获得的最大连通性在哪里,从这个虚点的叶子节点里选一个到111号点路径最短的点即可,这一步可以在dfsdfsdfs的时候预处理出来,这样每次查询就可以用倍增lognlognlogn实现

代码
cpp 复制代码
const int N=4e5+10;

struct edge{
	int u,v,w;
	bool operator < (const edge&t)const
	{
		return w>t.w;
	}
};

struct node{
	ll v,w;
};
vector<node>e[N];
int p[N];
vector<int>tr[N];
int fa[N][20];
ll mndis[N];


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

void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=2*n;i++)
	{
		e[i].clear();
		tr[i].clear();
		p[i]=i;
		mndis[i]=INF;
		for(int j=0;j<=19;j++) fa[i][j]=0;
	}
	
	
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		e1[i]={a,b,d};
		e[a].pb({b,c});
		e[b].pb({a,c});
	}
	
	sort(all(e1));
	int cnt=n;
	vector<int>w1(2*n+1);
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		
		if(x!=y)
		{
			cnt++;
			tr[cnt].pb(x);
			tr[x].pb(cnt);
			tr[cnt].pb(y);
			tr[y].pb(cnt);
			p[x]=cnt;
			p[y]=cnt;
			w1[cnt]=w;
		}
	}
	
	
	priority_queue<PII,vector<PII>,greater<PII>>pq;
	vector<ll>dis(n+1,INF);
	vector<int>vis(n+1);
	dis[1]=0;
	pq.push({0,1});
	while(!pq.empty())
	{
		auto [cost,cur]=pq.top();
		pq.pop();
		
		if(vis[cur]) continue;
		vis[cur]=1;
		
		for(auto [v,w]:e[cur])
		{
			if(cost+w<dis[v])
			{
				dis[v]=cost+w;
				pq.push({cost+w,v});
			}
		}
	}
	
	
	auto dfs=[&](auto &&dfs,int u,int father)->void
	{
		fa[u][0]=father;
		if(u<=n) mndis[u]=dis[u];
		
		for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
		for(auto ed:tr[u])
		{
			if(ed==father) continue;
			dfs(dfs,ed,u);
		}
		
		for(auto ed:tr[u]) mndis[u]=min(mndis[u],mndis[ed]);
		
	};
	
	
	for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(dfs,i,0);
	
	int q,k,s;
	cin>>q>>k>>s;
	int last=0;
	
	auto query=[&](int x,int y)
	{
		for(int i=19;i>=0;i--)
		{
			if(fa[x][i] && w1[fa[x][i]]>y) x=fa[x][i];
		}
		return mndis[x];
	};
	
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		x=(x+k*last-1)%n+1;
		y=(y+k*last)%(s+1);
		int ans=query(x,y);
		cout<<ans<<endl;
		last=ans;
	}
	
	
}

cf1706E

题意

给定nnn个点mmm条边的无向连通图以及qqq次询问,每次询问要要加入前几条边可以使[l,r][l,r][l,r]之间的所有点互相连通

思路

我们以边的编号为边权,按最小生成树重构图,可以发现此时的答案就是[l,r][l,r][l,r]内区间所有点的lcalcalca所对应的点权,问题变为怎么快速求出一段区间的lcalcalca, 利用dfndfndfn序不难发现[l,r][l,r][l,r]的lcalcalca就是dfnmax∈[l,r]dfn_{max} \in [l,r]dfnmax∈[l,r]和dfnmin∈[l,r]dfn_{min} \in [l,r]dfnmin∈[l,r]的lcalcalca, 只需要在dfsdfsdfs过程中为所有点分配dfndfndfn序并且建立dfndfndfn序到点编号的映射即可,[l,r][l,r][l,r]内dfndfndfn序最大和最小的点可以使用两个ststst表维护

代码
cpp 复制代码
const int N=2e5+10;

struct edge{
	int u,v,w;
	bool operator < (const edge&t) const
	{
		return w<t.w;
	}
};

int p[N];
int dep[N];
int fa[N][20];
int tot=0;
int dfn[N],idx[N];
int st1[N][21];
int st2[N][21];

vector<int>e[N];


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

void dfs(int u,int father)
{
	fa[u][0]=father;
	dep[u]=dep[father]+1;
	dfn[u]=++tot;
	idx[tot]=u;
	
	for(int i=1;(1<<i)<=dep[u];i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	
	for(auto ed:e[u])
	{
		if(ed==father) continue;
		dfs(ed,u);
	}
}

int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	
	for(int i=19;i>=0;i--)
	{
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
		if(u==v) return v;
	}
		
	for(int i=19;i>=0;i--)
	{
		if(fa[u][i]!=fa[v][i])
		{
			u=fa[u][i];
			v=fa[v][i];
		}
	}
	return fa[u][0];
}

int query_max(int l,int r)
{
	int k=log2(r-l+1);
	return max(st1[l][k],st1[r-(1<<k)+1][k]);
}

int query_min(int l,int r)
{
	int k=log2(r-l+1);
	return min(st2[l][k],st2[r-(1<<k)+1][k]);
}


void solve()
{
	int n,m,q;
	cin>>n>>m>>q;
	
	for(int i=1;i<=2*n;i++)
	{
		e[i].clear();
		p[i]=i;
		dep[i]=dfn[i]=idx[i]=0;
		for(int j=0;j<=19;j++) fa[i][j]=0;
	}
	tot=0;
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		e1[i]={a,b,i};
	}
	
	
	sort(all(e1));
	vector<int>w1(2*n+1);
	int cnt=n;
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			p[x]=cnt;
			p[y]=cnt;
			w1[cnt]=w;
		}
	}
	
	
	for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
	
	for(int i=1;i<=cnt;i++) st1[i][0]=st2[i][0]=dfn[i];
	
	for(int j=1;j<=20;j++)
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			st1[i][j]=max(st1[i][j-1],st1[i+(1<<(j-1))][j-1]);
			st2[i][j]=min(st2[i][j-1],st2[i+(1<<(j-1))][j-1]);
		}
	}
	
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		int x=query_max(l,r);
		int y=query_min(l,r);
		cout<<w1[lca(idx[x],idx[y])]<<" ";
	}
	cout<<endl;
	
}

2021上海H Life is a Game

题意

一张nnn点mmm条边的有权无向图,每个点有点权aia_iai, 每到达一个点,就可以使声望值增加aia_iai点,对于边jjj其边权为wjw_jwj,只有当前声望值>wj> w_j>wj时才可以通过边www, 给出qqq个询问,每次给出出发点和初始声望值,求从该点出发可以获得的最大声望值

1≤n,m,q≤1e51 \leq n,m,q \leq 1e51≤n,m,q≤1e5

思路

用最小生成树重构原图,处理每个虚点作为根节点时可以获得的总声望值 get_i ,对于每次询问倍增处理,从出发点跳到当前声望可以到达的最高点,即 res+get[u]≥w[u]res+get[u] \geq w[u]res+get[u]≥w[u], w\[u\]为重构之后的虚点点权,需要注意的是每次可能向上跳多次,因为每跳到新的一个点声望值都会增加,需要判断是否还能接着往上跳

代码
cpp 复制代码
const int N=2e5+10;

struct edge{
	int u,v,w;
	bool operator < (const edge &t)const
	{
		return w<t.w;
	}
};

int p[N];
vector<int>e[N];
int dep[N];
int fa[N][20];


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

void solve()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=2*n;i++) p[i]=i;
	
	vector<int>a(n+1);
	for(int i=1;i<=n;i++) cin>>a[i];
	
	vector<edge>e1(m+1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e1[i]={a,b,c};
	}
	
	sort(all(e1));
	vector<int>w1(2*n+1);
	int cnt=n;
	
	for(int i=1;i<=m;i++)
	{
		auto [u,v,w]=e1[i];
		int x=find(u);
		int y=find(v);
		if(x!=y)
		{
			cnt++;
			e[cnt].pb(x);
			e[x].pb(cnt);
			e[cnt].pb(y);
			e[y].pb(cnt);
			p[x]=cnt;
			p[y]=cnt;
			w1[cnt]=w;
		}
	}
	vector<ll>get(cnt+1);
	
	auto dfs=[&](auto &&dfs,int u,int father)->void
	{
		fa[u][0]=father;
		if(u<=n) get[u]=a[u];
		for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
		
		for(auto ed:e[u])
		{
			if(ed==father) continue;
			dfs(dfs,ed,u);
			get[u]+=get[ed];
		}
		
	};
	
	for(int i=1;i<=cnt;i++) if(i==p[i]) dfs(dfs,i,0);
	
	auto query=[&](int x,ll y)
	{
		ll res=y+a[x];
		int ok=1;
		while(ok)
		{
			ok=0;
			for(int i=19;i>=0;i--)
			{
				if(fa[x][i] && res>=w1[fa[x][i]])
				{
					x=fa[x][i];
					res=y+get[x];
					ok=1;
				}
			}
		}
		return res;
	};
	
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		//cout<<get[x]<<endl;
		cout<<query(x,y)<<endl;
	}
	
}
相关推荐
爱编程的鱼5 小时前
OpenCV Python 绑定:原理与实战
c语言·开发语言·c++·python
未来之窗软件服务10 小时前
自己写算法(九)网页数字动画函数——东方仙盟化神期
前端·javascript·算法·仙盟创梦ide·东方仙盟·东方仙盟算法
豐儀麟阁贵10 小时前
基本数据类型
java·算法
Larry_Yanan12 小时前
QML学习笔记(三十四)QML的GroupBox、RadioButton
c++·笔记·qt·学习·ui
程序员老舅12 小时前
干货|腾讯 Linux C/C++ 后端开发岗面试
linux·c语言·c++·编程·大厂面试题
乐迪信息12 小时前
乐迪信息:基于AI算法的煤矿作业人员安全规范智能监测与预警系统
大数据·人工智能·算法·安全·视觉检测·推荐算法
程序员Aries12 小时前
自定义网络协议与序列化/反序列化
linux·网络·c++·网络协议·程序人生
Pafey13 小时前
MFC中一个类的成员变量值自动被篡改:多重继承带来的问题
c++·mfc
hsjkdhs13 小时前
C++之多层继承、多源继承、菱形继承
开发语言·c++·算法