【WC 2002】奶牛浴场 解题报告


奶牛浴场
happy.exe

【问题描述】
由于John建造了牛场围栏,激起了奶牛的愤怒,奶牛的产奶量急剧减少。为了讨好奶牛,John决定在牛场中建造一个大型浴场。但是John的奶牛有一个奇怪的习惯,每头奶牛都必须在牛场中的一个固定的位置产奶,而奶牛显然不能在浴场中产奶,于是,John希望所建造的浴场不覆盖这些产奶点。这回,他又要求助于Clevow了。你还能帮助Clevow吗?
John的牛场和规划的浴场都是矩形。浴场要完全位于牛场之内,并且浴场的轮廓要与牛场的轮廓平行或者重合。浴场不能覆盖任何产奶点,但是产奶点可以位于浴场的轮廓上。
Clevow当然希望浴场的面积尽可能大了,所以你的任务就是帮她计算浴场的最大面积。

【输入文件】
       输入文件的第一行包含两个整数LW,分别表示牛场的长和宽。文件的第二行包含一个整数n,表示产奶点的数量。以下n行每行包含两个整数xy,表示一个产奶点的坐标。所有产奶点都位于牛场内,即:0<x<L0<y<W

【输出文件】
       输出文件仅一行,包含一个整数S,表示浴场的最大面积。

【输入输出样例】
happy.in
happy.out
10 10
4
1 1
9 1
1 9
9 9
80


【参数约定】
  0<n<5000
  1<L,W<30000

【解析】
       不难看出,为了取得最大面积,矩形的四条边必须经过产奶点或边界。
       于是考虑贪心,假设产奶点AB$y_A<y_B$)分别位于最终取得的矩形的左侧和右侧,点C满足(1)$x_A<x_C<x_B$(2)$\forall P$满足$x_A<x_P<x_B$且$y_P<y_A,\;$有$y_P\leqslant y_C$。通俗地说,点C是直线$x=x_A$和直线$x=x_B$之间位于点A下方的最高的点。类似地,点D满足(1)$x_A<x_D<x_B$(2)$\forall P$满足$x_A<x_P<x_B$且$y_P<y_A,\;$有$y_P\leqslant y_C$。我们便得到了一个以AB为左右边界的矩形。如图:

       这样,只要枚举所有的AB即可得到答案。至于CD,可以用平衡树确定。
       具体是实现方式是:将所有的产奶点按x坐标排升序,枚举A,在A的右侧枚举B,将By坐标加入map <int,int> S中,使用map::lower_bound()map::upper_bound()确定CD,当然假如CD不存在,矩形的下界或上界就是区域的边界,更新答案。
       这种做法虽然是正确的,但复杂度与实现难度都较大,而且不便于判断与边界重合的情况。
       我进一步简化枚举过程:引入辅助变量uplow,分别表示$y_D$$y_C$,枚举A,在A的右侧枚举B,用By坐标更新uplow,更新答案。实现如下:
for (int i = 0; i<n; i++)
{
    int up = W, low = 0;
    int j = i;
    while (point[j].x == point[i].x)j++;
    int lastsame = j;
    for (; j<n; j++)
    {
        if (point[j].x != point[lastsame].x)
        {
            for (int k = lastsame; k<j; k++)
            {
                if (point[k].y >= point[i].y)up = min(up, point[k].y);
                else low = max(low, point[k].y);
            }
            lastsame = j;
        }
        ans = max(ans, (point[j].x - point[i].x)*(up - low));
    }
    ans = max(ans, (L - point[i].x)*(up - low));
}
       现在只剩下区域的左侧位于边界上的特殊情况了,这种情况不好直接处理,一个比较容易的方法就是竖着再枚举一遍。
       但这样处理还会遗漏一种更加特殊的情况:矩形的四条边都位于边界上,对于这种情况,n一定为0,直接特判即可。
【参考程序】
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;

void read(int &x)
{
    x = 0; char ch = getchar(); bool f = 0;
    while (ch>'9' || ch<'0') {if(ch=='-')f=1;ch=getchar();}
    while (ch >= '0'&&ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
    if (f)x = -x;
}

struct point_t
{
    bool operator<(const point_t &b)const
    {
        if (x != b.x)return x<b.x;
        return y<b.y;
    }
    int x, y;
}point[5000];

int main()
{
    freopen("happy.in", "r", stdin);
    freopen("happy.out", "w", stdout);
    int n, L, W;
    read(L); read(W);
    read(n);
    if (n == 0)
    {
        cout << L*W << endl;
        return 0;
    }
    for (int i = 0; i<n; i++)
    {
        read(point[i].x);
        read(point[i].y);
    }
    sort(point, point + n);
    int ans = 0;
    for (int i = 0; i<n; i++)
    {
        int up = W, low = 0;
        int j = i;
        while (point[j].x == point[i].x)j++;
        int lastsame = j;
        for (; j<n; j++)
        {
            //这么做是为了防止有多个点的x坐标相同
            if (point[j].x != point[lastsame].x)
            {
                for (int k = lastsame; k<j; k++)
                {
                   if(point[k].y>=point[i].y)up = min(up, point[k].y);
                   else low = max(low, point[k].y);
                }
                lastsame = j;
            }
            ans = max(ans, (point[j].x - point[i].x)*(up - low));
        }
        if (L != point[lastsame].x)
        {
            for (int k = lastsame; k<n; k++)
            {
                if (point[k].y >= point[i].y)up = min(up, point[k].y);
                else low = max(low, point[k].y);
            }
        }
        ans = max(ans, (L - point[i].x)*(up - low));
    }
    /*
    还剩下的竖着枚举的情况交给读者自己实现。
    如果不写,也可以通过官方数据。
    但不保证在OJ上提交可以通过。
    */
    cout << ans << endl;
    return 0;
}

没有评论:

发表评论