acm-(gcd、思维)第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(上海)L.Traveling in the Grid World

题面
传送门
g c d ( n , m ) = 1 gcd(n,m)=1 gcd(n,m)=1的时候,容易发现直接走直线即可,并且不会经过任何网格点,显然是一条最短路。

g c d ( n , m ) ≠ 1 gcd(n,m)\ne 1 gcd(n,m)=1的时候,暴力枚举 ( 0 , 0 ) (0,0) (0,0) ( n , m ) (n,m) (n,m)连线的路径附近的整点,考虑将它们作为转折点,就像下图一样(标记红的点就是要枚举的转折点):
图一
那么为什么答案一定是出现在以这些红点中的某一个点为转折点的路径下呢?
先考虑以一个点为转折点的最短路径的情况,设起点为 A A A,终点为 B B B,我们发现我们一定能够在这些红点中找到至少一个点 C ( x , y ) C(x,y) C(x,y)满足 g c d ( x , y ) = 1 且 gcd(x,y)=1且 gcd(x,y)=1 g c d ( n − x , n − y ) = 1 gcd(n-x,n-y)=1 gcd(nx,ny)=1,因为如果 A C AC AC路径上有一个整点 C ′ C' C的话,我们可以找到一条新的路径 A C ′ B AC'B ACB A C B ACB ACB还短,并且不与直线 A B AB AB重合, C B CB CB上有整点的话也是同理的,然后若 C C C是红点,那么 C ′ C' C必定是红点,也就是说通过 C C C迭代下去的最终结果其实也是个红点,而且这个迭代必定是有结束的时候的,故在红点中存在一个点可以作为转折点,而所有不以红点为转折点(假设以 D D D为转折点)的路径与 A B AB AB形成的三角形 A D B ADB ADB中必定包含至少一个红点 D ′ D' D,那么选择 D ′ D' D一定是比 D D D更加优的,那么我们通过 D ′ D' D进行上述迭代最终一定能够找到一个红点作为转折点,注意到迭代的过程中路径的长度一定是递减的,因此一个转折点的情况下,选择红点一定最优,而且那个最优的转折点就在红点中,因此暴力枚举红点即可。

再考虑大于两个点作为转折点的时候的情况,我们考虑这时候是否会比一个转折点的时候更优。首先这些转折点一定形成的是一个凸包,如果不是凸包,那么一定不优,假设形成的凸包是 A C 1 C 2 . . . C k B AC_1C_2...C_kB AC1C2...CkB,其中 C 1 ∼ k C_{1\sim k} C1k是路径上的 k k k个转折点,那么显然选择 A C i B AC_iB ACiB的路径更优,并且对于任意的 i i i对应的路径 A C i B AC_iB ACiB一定可以通过迭代找到一个更加适合的转折点,因此一个转折点一定是最优的情况。

综上来看,我们知道答案的形式只可能是一个转折点,并且这一个转折点必定出现在红点(即斜线附近的点)的某一个点中,而这些点数量不是很多,暴力枚举即可。

#include <bits/stdc++.h>
using namespace std;

typedef double db;
const db inf = 1e100;

db dis(db r1,db c1,db r2,db c2){
    return sqrt((r1-r2)*(r1-r2)+(c1-c2)*(c1-c2));
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        if(__gcd(n,m)==1)printf("%.15lf\n",sqrt(1ll*n*n+1ll*m*m));
        else{
            db ans=inf;
            for(int i=1;i<=m;++i){
                if(1ll*i*n%m==0){
                    db d1=dis(0,0,1ll*i*n/m-1,i)+dis(1ll*i*n/m-1,i,n,m);
                    db d2=dis(0,0,1ll*i*n/m+1,i)+dis(1ll*i*n/m+1,i,n,m);
                    ans=min(ans,min(d1,d2));
                }else{
                    db d1=dis(0,0,1ll*i*n/m,i)+dis(1ll*i*n/m,i,n,m);
                    db d2=dis(0,0,1ll*i*n/m+1,i)+dis(1ll*i*n/m+1,i,n,m);
                    ans=min(ans,min(d1,d2));
                }
            }
            swap(n,m);
            for(int i=1;i<=m;++i){
                if(1ll*i*n%m==0){
                    db d1=dis(0,0,1ll*i*n/m-1,i)+dis(1ll*i*n/m-1,i,n,m);
                    db d2=dis(0,0,1ll*i*n/m+1,i)+dis(1ll*i*n/m+1,i,n,m);
                    ans=min(ans,min(d1,d2));
                }else{
                    db d1=dis(0,0,1ll*i*n/m,i)+dis(1ll*i*n/m,i,n,m);
                    db d2=dis(0,0,1ll*i*n/m+1,i)+dis(1ll*i*n/m+1,i,n,m);
                    ans=min(ans,min(d1,d2));
                }
            }
            printf("%.15lf\n",ans);
        }
    }




}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值