[NOIP2013]货车运输
一.前言
一开始还因为写了两个add调了半天,之后又被同机房dalao hack掉了,慢慢改完又丧心病狂地压行还把快读压出 UKE ,总而言之,蛮痛苦的做起来。放个链接
做题复现
二.初步思路
啊这,这个怎么做,反手一个 floyd 魔改方程帅气出手,啊不行不行,就算要用最短路某d开头算法看起来,也要牛批一点好吧。然后魔改魔改,打出了一个?(也不知道写没写对hhh
void di(int s){
bool c[10005];
priority_queue<pair<int,int> > q;
q.push(make_pair(0,s));
while(!q.empty()){
int u=q.top().second;
q.pop();
for(int i=head[u];i;i=ne[i]){
int v=to[i];
int p=dis[i];
if(f[s][u]!=0)p=min(p,f[s][u]);
if(p>f[s][v]){
f[s][v]=p;
if(!c[v]){
c[v]=1;
q.push(make_pair(f[s][v],v));
}
}
}
}
}
然后转念一想,哎嘿,堆,路径上的最小值,这是prim啊!所以最小(其实最大)生成树浮现在了我眼前?
然后就想出了结论,答案肯定在最大生成树上。这样才保证两个点之间路径上的最小值最大。于是求一个最大生成树
三.思路进一步
既然是最大生成树,用什么prim,反手kruskal埋伏他一手。然后稍微想想,对于一个图,最大生成树只有一个,根的话无所谓,扭曲一下就好。那么就方便的以1做个根吧!于是在原图上先求出最大生成树,把原图舍弃建一个新图(就是这里两个add混乱了qwq)
void ku(){
for(int i=1;i<=n;++i)f[i]=i;
for(int i=1;i<=m;++i){
int x=e[i].l,y=e[i].r,f1=gf(x),f2=gf(y);
if(f1!=f2){
f[f1]=f2;
add(x,y,e[i].dis);
add(y,x,e[i].dis);
}
}
}
四.思路更近一步
对于最大生成树上的任意两点,我们要求出他们简单路径上的最小值的大小,既然已经用1为根,那么这两个点就不一定在一颗子树上。此时需要求出 LCA,然后分别在以 LCA 断开后的两条路径上找最小值。,我这里用的是倍增求LCA
对于倍增法来说,有 (f[x][i]) 表示 x 的第 (2^i)个父亲,那么顺着这个思路魔改一下,则有 (minn[x][i]) 表示从 x 到 x的第(2^i) 个父亲的路径上的最小值,这两个转移方程差不多其实。
那么在LCA的时候就求得出路径上最小值的大小啦!
五.奇奇怪怪的HACK
此时有一个很神奇的东西,就是这不一定是一个连通图,题目并没有说。那么虽然数据很水可以采用奇怪的方法,但是我们还是要考虑周全。
一般的求生成树都会有一个限制选 n-1 条边的条件以判断是否可以生成,但是很显然,我上面的代码里面没有这个东西,因为我其实求的是最大生成森林。根据并查集的性质,如果不限制边的话,就算原图不连通,也会形成一个最大生成森林,且每个生成树都在一个集合内,即他们有着同一个祖先。
那么再来看最后询问的时候,如果两个点不在同一个集合中,那么肯定走不到,输出-1就行,那么如果在一个集合中就看这个集合代表的树有没有预处理过,只有预处理过后才能LCA,如果没有,就以代表集合的那个祖先为根预处理就行。如果有,LCA输出答案。
六.CODE
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=50005;
int n,m,q;
int read(){
char ch=getchar();
int res=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
return res*f;
}
struct p{
int l,r,dis;
}e[MAXN];
void adde(int x,int y,int d,int i){e[i].l=x;e[i].r=y;e[i].dis=d;}
bool cmp(p a,p b){return a.dis>b.dis;}
int f[10005];
int gf(int x){
if(f[x]==x)return x;
return f[x]=gf(f[x]);
}
int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot;
void add(int x,int y,int d){dis[++tot]=d,to[tot]=y,ne[tot]=head[x],head[x]=tot;}
void ku(){
for(int i=1;i<=n;++i)f[i]=i;
for(int i=1;i<=m;++i){
int x=e[i].l,y=e[i].r,f1=gf(x),f2=gf(y);
if(f1!=f2){
f[f1]=f2;
add(x,y,e[i].dis);
add(y,x,e[i].dis);
}
}
}
int fa[10005][25],dep[10005],minn[10005][25];
void dfs1(int x,int pre){
dep[x]=dep[pre]+1;
for(int i=0;i<=18;i++)
fa[x][i+1]=fa[fa[x][i]][i],minn[x][i+1]=min(minn[x][i],minn[fa[x][i]][i]);
for(int i=head[x];i;i=ne[i]){
if(to[i]==pre)continue;
fa[to[i]][0]=x,minn[to[i]][0]=dis[i];
dfs1(to[i],x);
}
}
int LCA(int x,int y){
int ans=1<<30;
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;--i)
if(dep[fa[x][i]]>=dep[y])ans=min(ans,minn[x][i]),x=fa[x][i];
if(x==y)return ans;
for(int i=19;i>=0;--i)
if(fa[x][i]!=fa[y][i])
ans=min(ans,min(minn[x][i],minn[y][i])),x=fa[x][i],y=fa[y][i];
return min(ans,min(minn[x][0],minn[y][0]));
}
bool vis[10005];
int main(){
n=read();m=read();
for(int i=1,l,r,k;i<=m;++i){
l=read();r=read();k=read();
adde(l,r,k,i);
}
sort(e+1,e+m+1,cmp);
ku();
q=read();
for(int i=1,s,t;i<=q;++i){
s=read();t=read();
int f1=gf(s),f2=gf(t);
if(f1!=f2)cout<<"-1"<<endl;
else{
if(!vis[f1]){vis[f1]=1;dfs1(f1,0);}
cout<<LCA(s,t)<<endl;
}
}
return 0;
}
七.时间复杂度
虽然感觉上预处理了很多次,但是每个点只访问了一次,是(O(n)),然后有一个sort (O(mlogm)),LCA用了q次是(O(qlogn))所以总的不算慢。