题目链接:点击这里
思路
首先注意审题,题目说的是新加入的 m m 个雕塑可以放在任意位置,因此我们将新加入的雕塑就放在需要的地方(等间隔),只考虑之前的个雕塑的移动位置。由于周长已经确定,我们可以这样想:先把原有的 n n 个雕塑放在离其最近的新位置上,其余位置摆放新的雕塑,就可以得到移动总距离最小。
实现
因此我们很容易可以写出代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
int n,m;
double circle = 10000;
int main()
{
while (scanf("%d%d",&n,&m)==2)
{
int sum = n+m;
double ans = 0;
double each = circle/sum; //新的间隔
double origin = circle/n; //之前的间隔,一定大于新间隔
for (int i =1 ;i < n;i++)
{
double position = origin*i;
for (int j = 1;j < sum;j++) //计算离第i个雕塑最近的位置
{
if (position == j*each)
break;
double temp1 = (j-1)*each;
double temp2 = j*each;
if (position > temp1 && position < temp2)
{
ans += min(position - temp1,temp2 - position);
break;
}
}
}
printf("%.4f\n",ans);
}
return 0;
}
简化代码
读者可以看出,上面的代码较繁琐,因为我们每次去找第个雕塑的最适位置时都是用的一个for循环,代码复杂度为
O(n∗m)
O
(
n
∗
m
)
,实在是太高。
书上有一种很巧妙的思路:先把圆看成是单位圆,利用通分的思想找出离第
i
i
个雕塑移动的最短距离,最后乘上系数即可。
先看代码:
#include <cstdio>
#include <cmath>
using namespace std;
int main()
{
int n,m;
while (scanf("%d%d",&n,&m) == 2)
{
double ans = 0;
for (int i= 1;i < n; i++)
{
double pos = (double)i / n * (n+m); //计算每个需要移动的雕塑的坐标
ans += fabs(pos - floor(pos+0.5)) / (n+m); //累加移动距离
}
printf("%.4f\n",ans*10000);
}
return 0;
}
代码的确是很简洁,但我看了好久都没看懂大佬是如何实现的。。。于是动动手指头算了一算:
首先令,当
i=1
i
=
1
时,计算第一个雕塑距离为
12
1
2
,我们知道最后的位置的分母一定是5,因此先通分:
12=525 1 2 = 5 2 5
可以看出,代码中的pos
即为分母。
因此,我们要使得分母的
52
5
2
为整数,即找与
52
5
2
最近的整数即可,这就是代码中fabs(pos - floor(pos+0.5))
的意义。
分析与证明
题并不困难,但仔细想想,还有两个问题没有解决。
1.为什么我们不考虑第一个( i=0 i = 0 )雕塑?
从直觉上讲,第一个雕塑的位置永远不会移动,事实上也的确如此。可以将圆想象成一个数轴,把第一个点放在圆点,左边的点放在负半轴,右边的点放在正半轴,可以简单证明,其中位数和平均数一定都为圆点。
2.有没有可能两个雕塑都要移动到同一个新位置?
可以简单证明:如果两个雕塑( m,n m , n )都要移动到同一个位置,因此,记该位置为 j j ,相邻两个位置为, j+1 j + 1 ,因此,雕塑 m m 和雕塑分别到 j−1 j − 1 和 j+1 j + 1 的位置都小于新的间隔距离的一半,因此雕塑 m m 和雕塑的距离小于新的间隔距离,这与题意相违背,因此增加了雕塑后,两个雕塑的距离不可能大于原雕塑之间的距离。