输油管道问题 (POJ - 1723)

输油管道问题 (POJ - 1723)

   先看算导上输油管道问题的描述:


   这个题,虽然说给出了井的x,y坐标,但是要修建的主管道却只是一条横向的,而且其余管道也只是到这条管道的竖向距离。
那么,就转换为确定一条直线 y = m,使得其它个点到这条直线的距离最多。也许不需要多的提示,大家的直觉就会想到应该
选所有y值的中点。但是,这个的证明却不是那么的明显。

证明如下:
   设所有的y值系列为y1,y2,...,yn,并且假设这个是按递增排列的。我们要求的是Sum =  Σ|yi-m|(1<=i<=n),
   
   1)显然假如选小于y1或者大于yn的y=m都不会比选y1或者yn更好。
   2)如果选y1或者yn,那么|y1-m|+|yn-m| = |yn-y1|都是一样的结果,甚至选y1和yn之间的任意一个值。
   3)如此继续下去,对于y2和yn,也有2)所描述的性质
   4)继续到最后,只需要取最中间一对点之间的值即可,如果n是奇数,那么就是中间的点,如果n是偶数,取任意一个中间
        点都可以


   通过上面证明,我们可以选取第y(n/2 + 1)作为修建主管道的地方。当然这可能是唯一的最优选择,或者无数个最优选择中的一个。
那么现在已经转换为求中位数了,求中位数的办法最简单的是对序列排序然后取中间的即可。算法导论上有一种平均代价 O(n)的办法,
思路类似于快速排序,快排的每一次操作都是划分数组,前小后大,如果我们也这一次次去划分数组,刚好轴元素处于我们要求的那个位置
上那么就达到我们的目的了,下面的代码中 Select函数就是求一个数组的中位数。


   对于POJ 1723题,很显然y的选择是中位数即可,x的选择需要转换一下也变成求中位数了。题目中描述,最后要达到的效果是每个士
兵都占成一横排,而且彼此相邻,也就是y相同,但是x系列是k,k+1,k+2,...,k+n-1。那么如何从原来的x0,x1,x2,...,x(n-1)移动过去了。
可以简单的考虑下, 将最左边的士兵移动到k,次左的移动到k+1,...,最右边的移动到k+n-1,所需要的移动之和一定是最小的。那么我们
可以将原来的x0-x(n-1)排序,得到x'0,x'1,...,x'(n-1),要求的 Sum =  Σ|x'i - (k + i)| = Σ|(x'i - i) -  k|,那么要使Sum最小,只需要
求序列X'i - i的中位数即可了。

代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using std::sort;
using std::swap;
#define MAX (10000 + 10)
int Partion(int* pnA, int nLen)
{
    int i, j;
    for (i = j = 0; i < nLen - 1; ++i)
    {
        if (pnA[i] < pnA[nLen - 1])
        {
            swap(pnA[i], pnA[j++]);
        }
    }
    swap(pnA[j], pnA[nLen - 1]);
    return j;
}
int Select(int* pnA, int nLen, int nIndex)
{
    if (nLen > 1)
    {
        int nP = Partion(pnA, nLen);
        if (nP + 1 == nIndex)
        {
            return pnA[nP];
        }
        else if (nP + 1 > nIndex)
        {
            return  Select(pnA, nP, nIndex);
        }
        else
        {
            return Select(pnA + nP + 1, nLen - nP - 1, nIndex - nP - 1);
        }
    }
    else
    {
        return pnA[0];
    }
}
int main()
{
    int nX[MAX];
    int nY[MAX];
    int nN;
    int i;
    while (scanf("%d", &nN) == 1)
    {
        for (i = 0; i < nN; ++i)
        {
            scanf("%d%d", &nX[i], &nY[i]);
        }
        int nMY = Select(nY, nN, nN / 2 + 1);
        sort(nX, nX + nN);
        for (i = 0; i < nN; ++i)
        {
            nX[i] = nX[i] - i;
        }
        int nMX = Select(nX, nN, nN / 2 + 1);
        int nSum = 0;
        for (i = 0; i < nN; ++i)
        {
            nSum += abs(nX[i] - nMX);
            nSum += abs(nY[i] - nMY);
        }
        printf("%d\n", nSum);
    }
    
    return 0;
}

你可能感兴趣的:(输油管道问题 (POJ - 1723))