P6765 [APIO2020] 交换城市
传送
很有意思的一个题!
首先观察无解情况。很显然不连通是无解的,联通块是一条链也是无解的。
剩下的都有解。因为要么有环要么有点度数 \(>3\),有环就随便转一圈,没环就随便找个度数 \(>3\) 的点当中转站,两辆车跑到不同分支上再往回返。
然后考虑一下二分答案的思想,保留对应的边用上面的方法判断有无解。但是二分答案显然是太慢了!
再观察一下,这就是一个瓶颈边问题,考虑 Kruskal 重构树。对于树上的每个点记录其最近的对应联通块有解的祖先,查询的时候在重构树上找两个点的 lca,再查询 lca 对应的答案就行。
But 这有个问题。怎么记录成环的情况?
考虑 Kruskal 进行的过程。如果扫到一条边,两个点都联通了,那查询一下联通块是不是一根链,如果是那就打标记表示不再是链,然后更新答案,不然无视掉;如果不连通,有一个联通块不是链子那合并完也不是,否则看这两个点是不是所在两个联通块的端点再判断。
然后因为要维护点所在的联通块,所以用启发式合并。总复杂度 \(O((n+q)\log n)\),飞快。
#include<bits/stdc++.h>
using namespace std;
struct edge{
int u,v,w;
bool operator < (const edge & B) const {
return w<B.w;
}
};
bool ntl[500005];//not link
int n,st[500005],en[500005],rt[500005],fa[500005][20],dep[500005],ff[500005];
vector<int> nd[500005],tr[500005];
vector<edge> ed;
int find(int x){
if(ff[x]==x)return x;
return ff[x]=find(ff[x]);
}
void dfs(int u,int f){
fa[u][0]=f;
for(int i=1;i<20;i++)fa[u][i]=fa[u][i-1]==-1?-1:fa[fa[u][i-1]][i-1];
rt[u]=f==-1?u:rt[f];
dep[u]=f==-1?1:dep[f]+1;
for(int i=0;i<tr[u].size();i++)dfs(tr[u][i],u);
}
int LCA(int x,int y){
if(rt[x]!=rt[y])return -1;
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;i--)if(fa[x][i]!=-1&&dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
for(int i=19;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
void init(int N,int M,vector<int> U,vector<int> V,vector<int> W){
n=N;
for(int i=0;i<M;i++)ed.push_back({U[i],V[i],W[i]});
sort(ed.begin(),ed.end());
for(int i=0;i<N;i++)ff[i]=i;
for(int i=0;i<N;i++)st[i]=en[i]=rt[i]=i,nd[i].push_back(i);
for(int i=0;i<M;i++){
int u=ed[i].u,v=ed[i].v;
int fu=find(u),fv=find(v);
if(nd[fu].size()<nd[fv].size()){swap(u,v),swap(fu,fv);}
if(fu==fv){
if(!ntl[fu]){
ntl[fu]=1;
for(int j=0;j<nd[fu].size();j++)tr[i+N].push_back(nd[fu][j]);
rt[fu]=i+N;
}
continue;
}
if(ntl[fu]||ntl[fv]){
if(ntl[fu])tr[i+N].push_back(rt[fu]);
else for(int j=0;j<nd[fu].size();j++)tr[i+N].push_back(nd[fu][j]);
if(ntl[fv])tr[i+N].push_back(rt[fv]);
else for(int j=0;j<nd[fv].size();j++)tr[i+N].push_back(nd[fv][j]);
ntl[fu]=1,rt[fu]=i+N;ff[fv]=fu;
}
else if((u==st[fu]||u==en[fu])&&(v==st[fv]||v==en[fv])){//link
st[fu]=u^st[fu]^en[fu];
en[fu]=v^st[fv]^en[fv];
for(int j=0;j<nd[fv].size();j++)nd[fu].push_back(nd[fv][j]);
ff[fv]=fu;
}
else{
ntl[fu]=1;
for(int j=0;j<nd[fu].size();j++)tr[i+N].push_back(nd[fu][j]);
for(int j=0;j<nd[fv].size();j++)tr[i+N].push_back(nd[fv][j]);
ff[fv]=fu;rt[fu]=i+N;
}
}
for(int i=N+M-1;i>=0;i--)if(!dep[i])dfs(i,-1);
}
int getMinimumFuelCapacity(int X,int Y){
int u=LCA(X,Y);
if(u==-1)return -1;
return ed[u-n].w;
}
int N,M,Q,X,Y;
vector<int>U,V,W;
signed main(){
cin>>N>>M;
U.resize(M),V.resize(M),W.resize(M);
for(int i=0;i<M;i++)cin>>U[i]>>V[i]>>W[i];
init(N,M,U,V,W);
cin>>Q;
while(Q--){
cin>>X>>Y;
cout<<getMinimumFuelCapacity(X,Y)<<endl;
}
}