1. 程式人生 > 實用技巧 >[NOIP2013]貨車運輸

[NOIP2013]貨車運輸

[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)\)所以總的不算慢。