《训练指南》——6.15

时间:2022-11-13 01:14:48

Uva 11300:

  圆桌旁坐着n个人,没人有一定数量的金币,基尼总数能被n整除。每个人可以给他左右相邻的人一些金币,最重视的每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。

  分析:似乎有约瑟夫环的即视感,但是模拟出来的情形显然要复杂的多而且无法控制“最少”这个指标,而达到“最小”的有力工具是贪心、dp或者数学,而这里貌似与dp和贪心没有多大关联,因此我们要考虑进行数学建模。

  对于逆时针给出的n个人手中的初始金币我们记作A[i],并且对每个人从1开始进行标号。每人手里最终得到的金币是M个,我们定义i号给i+1号的金币x[i]个(注意我们赋予x[i]矢量性,即如果x[i]是负数依然有意义,表示i+1号给了i号|xi|个金币),容易得到下面的方程:

  A[i] + x[i-1] – x[i] = M.

  基于这个等式我们会列出如下的方程组:

  A[1] + x[n] - x[1] = M

  A[2] + x[1] –x[2] = M

  A[3] + x[2] –x[3] = M

  A[4] + x[3] – x[4] =M

  ……

  我们不难进行归纳,x[i] = x[i-1] - M +A[i].

  考虑到上文中我们给出的关于x[i]的矢量性定义,即我们要求min{∑|x[i]|}。我们再基于递推式,将其迭代进入所求表达式,会将其表述成如下形式:

  f(x1) = |x1-c1| + |x1 – c2| + |x1 – c3| + |x1 – c4|… =∑|x - ci|(ci是基于递推式得到的常数,ci = -A[2]-A[3]-…-A[i] + (i-1)M。求f(x)的最小值。

  观察到绝对值,我们再考虑其几何意义,即我们能将问题转化成如下的形式:

  给出数轴上n个点(c1,c2,c3,c4….),求解某个点,使得x到这n个点的距离之和最小,求得x之后带入f(x1)即可。

  那么现在我们面临的最后问题,就是x怎么求?

  对于n个点中间形成的n-1个区间段,我们设置一个动点x’任移动,能够看到如果x’所在的区间,左边的点和右边的点个数不同,则必然能够移动x’使得距离和减小,那么我们按照这个使距离和减小的方向(其实就是点较多的那个方向),继续移动,会发现当左右点的数量相同的时候(对于奇数个点就是在最中间的那个点上),距离和不再减小,如果再往这个方向移动,很容易发现距离和又会增大,由此我们不难得出结论,x取得n个数的中位数的时候,该点到其余n个点的距离和最小,即f(x)取得最小。

  基于这层数学化的分析,我们容易进行如下的编程实现。

 

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 1000005;
int main()
{
    int n;
    long long A[maxn];
    long long C[maxn];
    while(scanf("%d",&n) != EOF)
    {
         long long sum1 = 0 , M;
          for(int i = 1;i<= n;i++)
          {
              scanf("%lld",&A[i]);
              sum1 += A[i];
          }
          M = sum1 / n;
          C[1] = 0;
          long long temp = 0;
          for(int i = 2;i <= n;i++)
          {
              temp -= A[i];
              C[i] = temp + (i-1)*M;
          }
          sort(C+1,C+1+n);
          long long x = 0;
            if(n%2 == 1)
               x = C[n/2+1];
            else
               x = C[n/2];
               //printf("%lld\n",x);


          long long sum2 = 0;
          for(int i = 1;i <= n;i++)
               sum2 += abs(x - C[i]);

          printf("%lld\n",sum2);
    }
}