[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;
}