[NOIP 2013 提高组] 货车运输

题目

介绍两种做法:1. 树链剖分 2. 启发式合并

前置操作

两种做法都需要生成最大生成树

  • 根据题意,我们可以知道,求的是所有连通路径中的最小值 的最大值,于是我们只需要求最大生成树,两点之间的路径的最小值即是所求

树链剖分

这里用树链剖分简直就是降维打击,转化成线段树求区间最小值罢了.

注意的一点是,我们需要将边权赋予成所指向节点的点权,所以查询(x,y)之间的边权时应该是新编号[d[x]+1,d[y]]

对于不连通的情况需要输出-1,因此我们设置一个虚点,所有点与他连接,边权为-1

点击查看代码
#include<bits/stdc++.h>

using namespace std;

const int maxn=2e4+10;
const int maxm=1e5+10;
int t[maxn],d[maxn];
int n,m;

int head[maxn],dep[maxn],top[maxn],siz[maxn];
int f[maxn],ans[maxn<<1],w[maxn],val[maxn];
int tot=0;
 
int son[maxn]; 
int cnt=0;
struct node{
	int v,next,w;
}e[maxn];
struct node1{
	int u,v,w;
}eb[maxm];
int minw[maxn];
int find(int x){
	if(x!=t[x]) return t[x]=find(t[x]);
	return x;
}
void add(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].next=head[u];
	e[cnt].w=w;
	head[u]=cnt;
}
bool cmp(node1 a,node1 b){
	return a.w>b.w;
}

void kr(){
	for(int i=1;i<=m;++i){
		int x,y,z;
		cin>>x>>y>>z;
		eb[i]={x,y,z};
	}
	for(int i=1;i<=n;++i)
		eb[++m]={n+1,i,-1},w[i]=INT_MAX;//初始化
	sort(eb+1,eb+1+m,cmp);
	for(int i=1;i<=n;++i) t[i]=i;
	for(int i=1;i<=m;++i){
		auto [u,v,w]=eb[i];
		if(find(u)!=find(v)){
			t[find(u)]=find(v);
			add(u,v,w);
			add(v,u,w);
		}
	}
	return ;
	
}
void dfs1(int u,int fa,int deep){
	int maxson=-1;
	f[u]=fa;dep[u]=deep;
	siz[u]=1;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa) continue;
		w[v]=e[i].w;
		dfs1(v,u,deep+1);
		siz[u]+=siz[v];
		if(maxson<siz[v]) 
			maxson=siz[v],son[u]=v;
	}
} 
void dfs2(int u,int topf){
	top[u]=topf;
	d[u]=++tot;
	val[tot]=w[u];//转化为点权
	if(!son[u]) return ;	
	dfs2(son[u],topf);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==f[u] || v==son[u]) continue;
		dfs2(v,v);  
	}
	
}
void push_up(int p){
	ans[p]=min(ans[p<<1],ans[p<<1|1]);
}
void build(int p,int l,int r){
	if(l==r){
		ans[p]=val[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	push_up(p);
}
int query(int p,int l,int r,int nl,int nr){
	if(nl<=l && r<=nr){
		return ans[p];
	}
	int res=INT_MAX;
	int mid=(l+r)>>1;
	if(nl<=mid) res=min(query(p<<1,l,mid,nl,nr),res);
	if(nr>mid) res=min(query(p<<1|1,mid+1,r,nl,nr),res);
	return res;
}
int lca(int x,int y){
	int res=INT_MAX;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		res=min(res,query(1,1,n+1,d[top[x]],d[x]));
		x=f[top[x]];
	}
	if(x==y) return res;
	if(d[x]>d[y]) swap(x,y);
	return min(res,query(1,1,n+1,d[x]+1,d[y])); //查询区间[d[x]+1,d[y]]
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	
	kr();
	dfs1(n+1,0,n+1);
	dfs2(n+1,n+1);
		
	build(1,1,n+1);
	int q;
	cin>>q;
	while(q--){
		int u,v;cin>>u>>v;		cout<<lca(u,v)<<"\n";
	}
	return 0;
}

启发式合并

这个做法打开了我们的思维,不只考虑生成之后的树来寻求答案,也要可以从生成的过程来得到答案

考虑建立最大生成树的过程,每一次的合并都会使得两个连通块通过一个边合并,所以如果两个连通块中存在查询的两个点,那么该边一定是路径上的最小边权

所以对于每个点,初始储存他的询问编号,每次建树的时,拿出来对比一下,如果有,就记录答案

每次合并之后,删除集合点少的集合,并入大集合之中

点击查看代码
#include<bits/stdc++.h>

using namespace std;

int n,m,q;
const int maxn=1e5+10;
const int maxm=5e5+10;

set<int>s[maxn];

struct node{
	int u,v,w;
	friend bool operator<(node a,node b){
		return a.w>b.w;
	}
}e[maxm];

int ans[maxm];

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

int main(){
	memset(ans,-1,sizeof(ans));
	cin>>n>>m;
	for(int i=1;i<=n;++i) f[i]=i;
	for(int i=1;i<=m;++i){
		int u,v,w;cin>>u>>v>>w;
		e[i]={u,v,w};
	} 
	sort(e+1,e+1+m);
	cin>>q;
	for(int i=1;i<=q;++i){
		int x,y;cin>>x>>y;
		s[x].insert(i);
		s[y].insert(i);//储存询问编号
	}  
	for(int i=1;i<=m;++i){
		auto [u,v,w]=e[i];
		int fu=find(u),fv=find(v);
		if(fu==fv) continue;
		if(s[fu].size()>s[fv].size()) swap(fu,fv);
		vector<int> temp;
		for(auto it:s[fu]){
			if(s[fv].count(it)){//两个集合分别有查询的点
				ans[it]=w;
				temp.push_back(it);//不着急就合并,可能后续还有需要查询的点
			}
			s[fv].insert(it);//既然之间没有需要查询的两点,合并
		}
        //合并
		for(auto x:temp) s[fv].insert(x);
		f[fu]=fv;
	}
	for(int i=1;i<=q;++i) cout<<ans[i]<<endl;
	return 0;
}
posted @ 2025-05-10 16:43  归游  阅读(9)  评论(0)    收藏  举报