题目大意
给定 n n n 个点,第 i i i 次会把所有编号满足 gcd ( a , b ) = m − i + 1 \gcd(a,b)=m-i+1 gcd(a,b)=m−i+1 的点对连边,对于每个询问 a , b a,b a,b,求点 a a a 和点 b b b 什么时候联通。
Solution
场上没做出来,还是逊了。
脑洞不够,套路不够。其实想通了还是蛮简单的。
首先我们考虑把连边转化一下。如果 gcd ( a , b ) = k \gcd(a,b)=k gcd(a,b)=k,那么显然 a a a 和 b b b 都是 k k k 的倍数。而每一次把 gcd ( a , b ) = k \gcd(a,b)=k gcd(a,b)=k 的所有点连边,最后的连通性与把所有的 k k k 的倍数是一样一样的。因此我们可以说,第 i i i 次把所有编号为 m − i + 1 m-i+1 m−i+1 的倍数的点之间连边。
然后我们可以发现,如果我们把每次加的边编号的话,那么两个点之间最早的联通时间是路径上的最大值。因此就转化为求两点间所有路径上的最大值最小是多少。
于是自然联想到 货车运输 这题。考虑每次按序加边,同时维护是一颗树。当然可以选择克鲁斯卡尔重构树,或者直接最小生成树都是可以的,我代码中给出克鲁斯卡尔重构树的写法。
还有一个就是路径最值,可以倍增维护,当然也可以用树剖(不写挂的话)。
Code
#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=2e5+10;
int f[MAXN],val[MAXN];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
vector<int> e[MAXN];
int fa[MAXN][20],dp[MAXN][20],dep[MAXN];
void dfs(int x,int fat){
fa[x][0]=fat;dp[x][0]=val[fat];
for(int i=1;i<20;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=1;i<20;i++)
dp[x][i]=max(dp[x][i-1],dp[fa[x][i-1]][i-1]);
for(int i=0;i<e[x].size();i++){
int s=e[x][i];if(s==fat) continue;
dep[s]=dep[x]+1;dfs(s,x);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
int dlt=dep[x]-dep[y];
for(int i=0;i<20;i++)
if(dlt&(1<<i))
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];
}
int main()
{
int n,m,Q;
scanf("%d%d%d",&n,&m,&Q);
int cnt=n;
for(int i=1;i<=n*2;i++)
f[i]=i;
for(int i=1;i<=m;i++){
int dis=m-i+1;
for(int j=dis;j+dis<=n;j+=dis){
int u=find(j),v=find(j+dis);
if(u==v) continue;
val[++cnt]=i;
f[u]=f[v]=cnt;
e[cnt].push_back(u);
e[cnt].push_back(v);
}
}
dfs(cnt,0);
int x,y;
while(Q--){
scanf("%d%d",&x,&y);
int lca=LCA(x,y);
int dlt=dep[x]-dep[lca];
int ans=0;
for(int i=0;i<19;i++)
if(dlt&(1<<i))
ans=max(ans,dp[x][i]),
x=fa[x][i];
dlt=dep[y]-dep[lca];
for(int i=0;i<19;i++)
if(dlt&(1<<i))
ans=max(ans,dp[y][i]),
y=fa[y][i];
printf("%d\n",ans);
}
}
End
校内模拟赛 T3,受益匪浅。