P4616 [COCI2017-2018#5] Pictionary
$ \texttt{Introduction} $
原题+思维
$ \texttt{Solution} $
本题题意可以简化为 $ i $ 和 $ j $ 之间连一条 $ m-\gcd(i,j)+1 $ 的边,然后问你从 $ a $ 走到 $ b $ 的道路中的最大值的最小值是多少。
简化之后的问题,难度就在于建边了,首先肯定不能建出一张单纯的图,我们发现答案要尽量的小,所以我们按价值从小到大加边,如果两个点已经相连,那么便不用再连,最后我们会得出一棵树。
但大体的思路怎么具体实现呢?
首先有一个很 $ naive $ 的想法,我们从大到小枚举最大公约数,然后找到最大公约数为这个数的两个点,按照上述的方法连边,但是这样时间复杂度会爆炸,亲测只有 $ 32pts $,那怎么办呢?
我们可以发现,选取的两个数一定都是最大公约数的倍数,那么我们直接将最大公约数和他的倍数连边,这样就把最大公约数是这个数的情况的边都连好了,也许你要问那如果会连多余的边呢,其实不会因为我们是要判断过得,如果最大公约数比这个数要大,那么前面一定已经连过边,就不会再连了。
建好这棵树之后就是经典操作了,我们要找两点之间的边的最大值,可以用倍增实现,具体可以参考[NOIP2013 提高组] 货车运输,然后这道题就做好了。
int find(int x)
{
if (ff[x]==x) return ff[x];
ff[x]=find(ff[x]);return ff[x];
}
void add(int x,int y,int z)
{
cnt++;a[cnt]=y;b[cnt]=d[x];c[cnt]=z;d[x]=cnt;
}
void sc(int x,int y) //找两点之间最大边
{
int t,i;
if (e[x]>e[y]){
t=x;x=y;y=t;
}
for (i=lg[n];i>=0;i--)
if (e[f[y][i]]>=e[x])
{
ans=max(ans,u[y][i]);
y=f[y][i];
}
if (x==y) return ;
for (i=lg[n];i>=0;i--)
if (f[x][i]!=f[y][i])
{
ans=max(ans,max(u[x][i],u[y][i]));
x=f[x][i];y=f[y][i];
}
ans=max(ans,max(u[x][0],u[y][0]));
}
int main()
{
//ios::sync_with_stdio(0);cin.tie();cout.tie();
n=read();m=read();T=read();
for (i=1;i<=n;i++) ff[i]=i;
for (i=m;i>=1;i--)
{
for (j=2;j<=n/i;j++)
{
x=i;y=i*j;
r1=find(x);r2=find(y);
if (r1!=r2)
{
add(x,y,m-i+1);add(y,x,m-i+1);
tot++;
}
ff[r1]=r2;
if (tot==n-1) break;
}
if (tot==n-1) break;
}//建图
for (i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
t=1;w=1;f2[1]=1;e[1]=1;
while (t<=w)
{
for (i=d[f2[t]];i;i=b[i])
{
if (e[a[i]]==0)
{
e[a[i]]=e[f2[t]]+1;
f[a[i]][0]=f2[t];u[a[i]][0]=c[i];
for (j=1;j<=lg[n];j++)
{
f[a[i]][j]=f[f[a[i]][j-1]][j-1];
u[a[i]][j]=max(u[a[i]][j-1],u[f[a[i]][j-1]][j-1]);
}
w++;f2[w]=a[i];
}
}
t++;
}//倍增的预处理
for (;T;T--)
{
x=read();y=read();ans=0;
sc(x,y);
printf("%d\n",ans);
}
return 0;
}

浙公网安备 33010602011771号