1. 程式人生 > 實用技巧 >[NOIP模擬]城市獵人

[NOIP模擬]城市獵人

題目

題目背景

\(n\) 個城市,標號為 \(1\)\(n\),修建道路花費 \(m\) 天,第 \(i\) 天時,若 \((a,b)=m-i+1\),則標號為 \(a\) 的城市和標號為 \(b\) 的城市會建好一條直接相連的道路,有多次詢問,每次詢問某兩座城市最早什麼時候能連通。

輸入描述

第一行輸入三個正整數 \(n,m,q\),其中 \(q\) 表示詢問個數。

接下來 \(q\) 行,每行兩個正整數 \(x,y\),表示詢問城市 \(x\) 和城市 \(y\) 最早什麼時候連通。

輸出描述

輸出 \(q\) 行,每行一個正整數,表示最早連通的天數。

題解

明白一點,兩點間聯通時間取決於它們之間最晚建好的一條邊,那麼我們可以有一個思路雛形:將需要建邊的點建邊,對於詢問兩點就找一下最大邊最小的路徑.

但是,如果我們暴力建邊,我們需要建的邊數可視地十分巨大,那麼我們能否有更好的方法建邊?

注意到每次建邊的點肯定是 \(m-i+1\) 的倍數,那麼我們建邊時就可以採用超級點的做法,將 \(m-i+1\) 與它的所有倍數建邊,這樣在取最大邊權的意義下,相當於它們之間都建了邊權為 \(i\) 的邊.

而處理詢問有更好的做法,我們可以將原來的圖放到帶權並查集上,對於並查集按秩合併,這樣高度不超過 \(\log n\),對於詢問的兩點,我們直接用類似暴力爬山處理即可.

建邊複雜度為 \(\mathcal O(m\log^2 n)\),詢問複雜度為 \(\mathcal O(q\log n)\).

#include<bits/stdc++.h>
using namespace std;
namespace IO{
	#define rep(i,l,r) for(int i=l,i##_end_=r;i<=i##_end_;++i)
	#define fep(i,l,r) for(int i=l,i##_end_=r;i>=i##_end_;--i)
	#define fi first
	#define se second
	#define Endl putchar('\n')
    #define writc(x,c) fwrit(x),putchar(c)
	typedef long long ll;
	typedef pair<int,int> pii;
	template<class T>inline T Max(const T x,const T y){return x<y?y:x;}
	template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
	template<class T>inline T fab(const T x){return x<0?-x:x;}
	template<class T>inline void getMax(T& x,const T y){x=Max(x,y);}
	template<class T>inline void getMin(T& x,const T y){x=Min(x,y);}
	template<class T>T gcd(const T x,const T y){return y?gcd(y,x%y):x;}
	template<class T>inline T readin(T x){
		x=0;int f=0;char c;
		while((c=getchar())<'0' || '9'<c)if(c=='-')f=1;
		for(x=(c^48);'0'<=(c=getchar()) && c<='9';x=(x<<1)+(x<<3)+(c^48));
		return f?-x:x;
	}
    template<class T>void fwrit(const T x){
        if(x<0)return putchar('-'),fwrit(-x);
        if(x>9)fwrit(x/10);putchar(x%10^48);
    }
}
using namespace IO;

const int maxn=1e5;

int fa[maxn+5],w[maxn+5],d[maxn+5],sz[maxn+5];

inline int find(const int u){
    if(fa[u]==u)return d[u]=1,u;
    int ret=find(fa[u]);
    d[u]=d[fa[u]]+1;
    return ret;
}

inline void add(const int u,const int v,const int c){
    // printf("add :> u == %d, v == %d, c == %d\n",u,v,c);
    int x=find(u),y=find(v);
    if(x!=y){
        if(sz[x]<sz[y])swap(x,y);
        getMax(sz[x],sz[y]+1);
        fa[y]=x,w[y]=c;
    }
}

int n,m,q;

signed main(){
    // freopen("pictionary.in","r",stdin);
    // freopen("pictionary.out","w",stdout);
    n=readin(1),m=readin(1),q=readin(1);
    rep(i,1,n)fa[i]=i,sz[i]=1,d[i]=1;
    rep(i,1,m){int now=m-i+1;
        for(int j=1;now*j+now<=n;++j)
            add(now,now*j+now,i);
    }
    while(q--){
        int u=readin(1),v=readin(1);
        int ans=0;
        find(u),find(v);// 要先將 d 更新
        if(d[u]<d[v])swap(u,v);
        while(d[u]>d[v])getMax(ans,w[u]),u=fa[u];
        while(u!=v){
            getMax(ans,Max(w[u],w[v]));
            u=fa[u],v=fa[v];
        }writc(ans,'\n');
    }
	return 0;
}