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);
}

posted @ 2021-03-10 18:37  ssdfzhyf  阅读(82)  评论(0编辑  收藏  举报