[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;
}

浙公网安备 33010602011771号