2020 ICPC Shanghai I

题目链接:
https://vjudge.net/contest/416227#problem/I
这个题要分2种情况讨论
1.m=1
按照题目的意思,由于只有一条直径,不会在圆心产生交点,中间的原点是不存在的,所以任意两个点间走线段即可,可以推一波公式,也可以枚举
2.m>1
由于这个图是旋转对称以及左右对称的,我们不用求出每两个点的间距,我们可以设(dis[i][j][k])表示从半径为(i)的同心圆走到半径为(j)的同心圆上,且两个点的极角的差的绝对值为(frac{kpi}{m})时,最短路径是多少,其中(jle ile n,kle m)
那么当(i>j),我们现在有一种普通的走法,就是从半径为(i)的圆,沿着直径走到半径为(j)的圆,然后再沿着圆弧走到对应位置,这个长度为(i-j+frac{jkpi}{m}),那么(dis[i][j][k])一定不大于它。
由于两个点极角的差的绝对值为(frac{kpi}{m}),我们必须在圆弧上走过对应的极角长度。假如我们在半径大于(j)的圆上走过了(frac{kpi}{m})的角度,那么圆弧长大于(frac{jkpi}{m}),然而在直径上肯定要走不少于(i-j)的长度,所以一定不是最优的。
那么,我们可以认为,走这段路一定经过了从半径为(i)的圆到半径为(j)的圆的直径,这个线段的长度为(i-j)。然后我们现在和终点在同一个圆上,极角为(frac{kpi}{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
",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
",ans);
}

原文地址:https://www.cnblogs.com/ssdfzhyf/p/14513516.html