P4616 [COCI2017-2018#5] Pictionary
阿新 • • 發佈:2021-09-07
$ \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; }