洛谷-P9830 題解

IOIAK_wanguan發表於2024-08-05

思路分析

分析樣例:

見紅線,長寬各為 2,存在格點;黃線長 2 寬 3,沒有格點。

考慮延長黃線使得長 4 寬 6,發現有格點。思考格點,如果長和寬都可以被分成 \(p\times l\) 的格式,則存在格點。那麼,就能想出:

推論 1:對於 \((0 \ , \ 0)\)\((x \ , \ y)\) 之間沒有格點,當且僅當 \(\gcd(x \ , \ y )=1\)

對推論 1 的證明:

若存在格點 \(A\),其座標為 \((a \ , \ b)\),由於在同一直線上,斜率 \(k\) 相同,則有 \(k=\dfrac{a}{b}=\dfrac{x}{y}\),即 \(b=\dfrac{a\times y}{x}\)。由於 \(b\) 為整數,則有 \(x \ | \ a\times y\)

採用反證法,\(\gcd(x \ , \ y )=1\) 時存在格點。

由於互質,\(x=\prod\limits_{i=1}^{s1}p_i^{c_i} \ , \ y=\prod\limits_{i=1}^{s2}q_i^{d_i}\),假設 \(x \ | \ y\)\(y\) 必然有因子 \(\prod\limits_{i=1}^{s1}p_i^{c_i}\),而實際上沒有,所以 \(y\) 對該式沒有貢獻。即:$x \ | \ a\times y \Leftrightarrow x \ | \ a $。

\(A\) 是線段上一點,有 \(a<x\),則 \(b\) 一定不屬於 \(\mathbb{Z^+}\),與原有條件衝突,由此可證。

得出推論 1 後,我們就能判斷兩點之間是否有格點了。那麼如何得出最短答案呢?

(圖是隨手畫的,具體有的性質以下文所述為準。)

見上,假設 \(AD\) 之間存在格點(在之後稱為不合法),於是我們找到任意一點 \(C\) 進行更新。

假設 \(C\) 為不合法,以圖為例,在 \(AC\) 上可以取一格點 \(B\),根據三角形定則 \(| \ BD \ |>| \ BC \ |+| \ CD \ |\),則 \(B\) 更優。假設無法在 \(AB\)\(BD\) 上取格點,那麼 \(B\) 的取點是合法的,可以得出:

推論 2:對於任意不合法的取點,必然可以在原線段取到更優的合法方案。

那麼就有:

推論 2.1:最優方案必然是合法的。

對推論 2.1 的證明:

假設最優方案不合法,根據推論 2,則有更優方案可以更新,與原有條件衝突,由此可得。

以上是一個轉折點的情況,那如果有多個轉折點呢?

如圖,多個轉折點的情況是不需要考慮的,見圖,由於三角形定則,紅色線的長度小於另外兩條邊之和。換句話說:

推論 3:最優方案只轉折一次或零次。

於是我們只要列舉一個點就可以了,如果在整個 \(n\times m\) 的範圍列舉,尋找最優方案,但這個時間複雜度顯然是不合理的。其實我們只需要列舉線段附近的點就可以,這樣複雜度就可以變成 \(\mathcal{O}(n)\)

程式碼實現

#define by_wanguan
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int t,n,m,y11,y2,y3,g; double dn,dm,nowm,ans,k;
double dis(double a,double b,double x,double y){
  return sqrt((x-a)*(x-a)+(y-b)*(y-b));
}
void solve(){
  k=(double)m/n;//斜率
  if((g=__gcd(n,m))==1)
    {printf("%.15lf\n",dis(0,0,n,m)); return ;}
  dn=n,dm=m;
  for(int i=1;i<n;i++){
    nowm=k*i;
    y11=(int)(nowm),y2=y11+1,y3=y11-1;//x座標為i時附近的點的y座標
    if(__gcd(n-i,m-y11)==1&&__gcd(i,y11)==1&&abs(y11-k*i)>1e-10)
    //判斷是否合法,abs()是在判斷是否為原線段上的點
	  ans=min(ans,dis(i,y11,n,m)+dis(0,0,i,y11));
    if(__gcd(n-i,m-y2)==1&&__gcd(i,y2)==1&&abs(y2-k*i)>1e-10)
      ans=min(ans,dis(i,y2,n,m)+dis(0,0,i,y2));
    if(__gcd(n-i,m-y3)==1&&__gcd(i,y3)==1&&abs(y3-k*i)>1e-10)
      ans=min(ans,dis(i,y3,n,m)+dis(0,0,i,y3));
  }
  printf("%.15lf\n",ans);
}
int main(){
  scanf("%d",&t); while(t--){
  scanf("%d%d",&n,&m);
  ans=1e9;
  solve();
}
} 

喵。

相關文章