CF1051F題解

zxh923發表於2024-07-11

The Shortest Statement

演算法:樹鏈剖分,最小生成樹,最短路。

先講一下題意:有一個 \(n\)\(m\) 邊的無向連通圖,\(q\) 次詢問,每次詢問 \(a\)\(b\) 的最短路長度。

資料範圍 \(1\le n,m\le 10^5,m-n\le 20\)

首先發現給了一個很奇怪的限制:\(m-n\le 20\),考慮他有什麼用。

我們在圖上跑全源最短路顯然會超時,但如果是一棵樹呢?顯然是好做的,寫一個 \(lca\) 就行了。

所以我們先求出來原圖的最小生成樹,這需要 \(n-1\) 條邊,那麼剩下的邊就不超過 \(m-(n-1)=21\) 條,所以至多連線了 \(21\times 2=42\) 個點。

於是我們對這些點跑單源最短路即可,使用 \(dijkstra\) 演算法,顯然不會超時。

下文把在最小生成樹上的邊稱為樹邊,否則稱為非樹邊,在非樹邊兩端的點稱為中轉點。

總結一下,我們可以把 \(a\)\(b\) 的最短路分成 \(2\) 類:

  • 不經過非樹邊:使用 \(lca\) 預處理就很好做了。

  • 經過非樹邊:用 \(dijkstra\) 預處理以中轉點為源點的單源最短路,列舉轉移即可。

下文給出了樹鏈剖分求 \(lca\) 的方法,用倍增實現也是可行的。

這裡說幾個我在寫這道題的程式碼中寫出來的的錯誤:

  • \(h\) 陣列未初始化為 \(-1\)

  • 排序時排序 \(e\) 陣列而不是 \(edge\) 陣列。

  • \(dfs2\) 中迴圈內的遞迴寫為 \(dfs2(j,u)\)

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define M 200005
#define K 45
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int n,m,Q,h[N],e[M],w[M],ne[M],idx;
int dep[N],fa[N],son[N],siz[N];
int top[N],dis[K][N];
int p[N],sum[N];
bool st[N];
vector<pii>E[N];
vector<int>spe;
struct node{
	int a,b,c;
	bool operator<(const node &t)const{
		return c<t.c;
	}
}edge[N];
int find(int x){
	if(p[x]!=x)p[x]=find(p[x]);
	return p[x];
}
void add(int a,int b,int c){
	e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
void dfs1(int u,int f){
	fa[u]=f;
	if(f!=-1)dep[u]=dep[f]+1;
	siz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u])continue;
		sum[j]=sum[u]+w[i];
		dfs1(j,u);
		siz[u]+=siz[j];
		if(!son[u]||siz[son[u]]<siz[j])son[u]=j;
	}
}
void dfs2(int u,int f){
	top[u]=f;
	if(son[u])dfs2(son[u],f);
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u]||j==son[u])continue;
		dfs2(j,j);
	}
}
int get_lca(int a,int b){
	while(top[a]!=top[b]){
		if(dep[top[a]]>=dep[top[b]])a=fa[top[a]];
		else b=fa[top[b]];
	}
	return dep[a]<dep[b]?a:b;
}
void dij(int s){
	memset(dis[s],0x3f,sizeof dis[s]);
	memset(st,0,sizeof st);
	priority_queue<pii,vector<pii>,greater<pii>>q;
	dis[s][spe[s]]=0;
	q.push({dis[s][spe[s]],spe[s]});
	while(!q.empty()){
		auto t=q.top().y;
		q.pop();
		if(st[t])continue;
		st[t]=1;
		for(auto eu:E[t]){
			int j=eu.x,c=eu.y;
			if(dis[s][j]>dis[s][t]+c){
				dis[s][j]=dis[s][t]+c;
				q.push({dis[s][j],j});
			}
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		p[i]=i;
	}
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		edge[i]={a,b,c};
		E[a].push_back({b,c});
		E[b].push_back({a,c});
	}
	sort(edge+1,edge+m+1);
	for(int i=1;i<=m;i++){
		int a=edge[i].a,b=edge[i].b,c=edge[i].c;
		int x=find(a),y=find(b);
		if(x!=y){
			p[x]=y;
			add(a,b,c);
			add(b,a,c);
		}
		else{
			spe.push_back(a);
			spe.push_back(b);
		}
	}
	for(int i=0;i<spe.size();i++){
		dij(i);
	}
	dfs1(1,-1);
	dfs2(1,1);
	cin>>Q;
	while(Q--){
		int a,b;
		cin>>a>>b;
		int lca=get_lca(a,b);
		int res=sum[a]+sum[b]-sum[lca]*2;
		for(int i=0;i<spe.size();i++){
			res=min(res,dis[i][a]+dis[i][b]);
		}
		cout<<res<<'\n';
	}
	return 0;
}