2020 ICPC Shanghai I - Sky Garden
题目链接:
https://vjudge.net/contest/416227#problem/I
这个题要分2种情况讨论
1.m=1
按照题目的意思,由于只有一条直径,不会在圆心产生交点,中间的原点是不存在的,所以任意两个点间走线段即可,可以推一波公式,也可以枚举
2.m>1
由于这个图是旋转对称以及左右对称的,我们不用求出每两个点的间距,我们可以设\(dis[i][j][k]\)表示从半径为\(i\)的同心圆走到半径为\(j\)的同心圆上,且两个点的极角的差的绝对值为\(\frac{k\pi}{m}\)时,最短路径是多少,其中\(j\le i\le n,k\le m\)
那么当\(i>j\),我们现在有一种普通的走法,就是从半径为\(i\)的圆,沿着直径走到半径为\(j\)的圆,然后再沿着圆弧走到对应位置,这个长度为\(i-j+\frac{jk\pi}{m}\),那么\(dis[i][j][k]\)一定不大于它。
由于两个点极角的差的绝对值为\(\frac{k\pi}{m}\),我们必须在圆弧上走过对应的极角长度。假如我们在半径大于\(j\)的圆上走过了\(\frac{k\pi}{m}\)的角度,那么圆弧长大于\(\frac{jk\pi}{m}\),然而在直径上肯定要走不少于\(i-j\)的长度,所以一定不是最优的。
那么,我们可以认为,走这段路一定经过了从半径为\(i\)的圆到半径为\(j\)的圆的直径,这个线段的长度为\(i-j\)。然后我们现在和终点在同一个圆上,极角为\(\frac{k\pi}{m}\),这段距离就是\(dis[j][j][k]\)
当\(i=j\)时,起点和终点在一个圆上,我们可以选择走圆弧,也可以选择向圆心走一段,然后走一个较小的圆弧,再回到那个半径为\(i\)的圆
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
double dis[510][510][510];
const double pi=acos(-1.0);
int main()
{
int n,m;
scanf("%d%d",&n,&m);
double ans=0;
if(m==1)
{
for(int i=1;i<=n;i++)
{
double ans1=0;
for(int j=1;j<i;j++)
{
for(int k=0;k<=1;k++)
{
dis[i][j][k]=i-j+(k)*2.0*j;
ans1+=dis[i][j][k];
}
}
ans1*=2;
dis[i][i][0]=0;
dis[i][i][1]=2*i;
ans1+=dis[i][i][1];
ans+=ans1;
}
printf("%.10lf\n",ans);
return 0;
}
for(int i=1;i<=n;i++)
{
for(int k=0;k<=m;k++)dis[i][0][k]=i;
for(int j=1;j<i;j++)
{
for(int k=0;k<=m;k++)dis[i][j][k]=i-j+dis[j][j][k];
}
for(int k=0;k<=m;k++)
{
dis[i][i][k]=(i*k*pi)/m;
for(int j=0;j<i;j++)dis[i][i][k]=min(dis[i][j][k]+i-j,dis[i][i][k]);
}
double ans1=0;
for(int j=1;j<i;j++)
{
for(int k=1;k<m;k++)ans1+=2*dis[i][j][k];
ans1+=dis[i][j][0]+dis[i][j][m];
}
ans1+=dis[i][0][0];
ans1=ans1*2*m;
double ans2=0;
ans2+=dis[i][i][m];
for(int k=1;k<m;k++)ans2+=2*dis[i][i][k];
ans2=ans2*m;
ans+=ans1+ans2;
}
printf("%.10lf\n",ans);
}