gym 101505 CTU Open Contest 2016 G Orchard Division

31 篇文章 1 订阅
19 篇文章 0 订阅

Problem

codeforces.com/gym/101505/attachments
vjudge.net/contest/187874#problem/G

Meaning

一个 m * m 的网格(长、宽下标 [ 0,m - 1])里有 n 个点,现要从四个角落中任意一个开始引出一个矩形,使得矩形内的点数恰好是总点数一半,且面积要尽量小,求这个最小面积,不存在输出 -1。

Analysis

枚举角落,以左下角为例。
从左到右枚举列(x),先一次性把该列的所有点全部加入考虑的“点集”,然后找当前的这个“点集”里按纵坐标 y 升序排的第 n2 个点的纵坐标(y),就得到一个矩形:从(0,0)到(x,y),如果这个矩形里恰好有 n2 个点,那这个矩形就是合法的候选解。
怎么维护这个“点集”可以快速找到第 n2 的点呢?可以用权值线段树。首先把所有纵坐标离散化,每加入一个点就把它的纵坐标插入到树中,就可以快速找第 n2 个点,并且可以查询那个纵坐标的区间里是否恰好有 n2 个点。
至于枚举角落,可以把整个 m * m 的大矩阵做旋转(即把点的坐标做变换),就可以把其它角落都转化为左下角。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6, M = 1e9;

struct pnt
{
    int x, y;

    pnt(int _x = 0, int _y = 0):
        x(_x), y(_y) {}

    bool operator < (const pnt &rhs) const
    {
        return x != rhs.x ? x < rhs.x : y < rhs.y;
    }
} p[N];

void rotate(int n, int m)
{
    for(int i = 0, x; i < n; ++i)
    {
        x = p[i].x;
        p[i].x = m - 1 - p[i].y;
        p[i].y = x;
    }
}

int tree[N+7<<2];

inline void pushup(int o)
{
    tree[o] = tree[o<<1] + tree[o<<1|1];
}

void update(int y, int l, int r, int rt)
{
    if(l == r)
    {
        ++tree[rt];
        return;
    }
    int m = l + r >> 1;
    if(m < y)
        update(y, m + 1, r, rt<<1|1);
    else
        update(y, l, m, rt<<1);
    pushup(rt);
}

// 找第 k 个纵坐标
int kth(int k, int l, int r, int rt)
{
    if(l == r)
        return l;
    int m = l + r >> 1;
    if(tree[rt<<1] >= k)
        return kth(k, l, m, rt<<1);
    else
        return kth(k - tree[rt<<1], m + 1, r, rt<<1|1);
}

// 查询纵坐标区间的点数
int query(int ql, int qr, int l, int r, int rt)
{
    if(ql <= l && r <= qr)
        return tree[rt];
    int res = 0, m = l + r >> 1;
    if(ql <= m)
        res += query(ql, qr, l, m, rt<<1);
    if(qr > m)
        res += query(ql, qr, m + 1, r, rt<<1|1);
    return res;
}

long long solve(int n, long long m)
{
    // 离散化纵坐标
    static int dsc[N];
    for(int i = 0; i < n; ++i)
        dsc[i] = p[i].y;
    sort(dsc, dsc + n);
    int ny = unique(dsc, dsc + n) - dsc;

    sort(p, p + n);
    memset(tree, 0, sizeof tree);
    // 先直接插入前 n / 2 - 1 个点
    for(int i = 0, y; i < n / 2 - 1; ++i)
    {
        y = lower_bound(dsc, dsc + ny, p[i].y) - dsc;
        update(y, 0, ny - 1, 1);
    }

    long long res = m * m;
    for(int i = n / 2 - 1, x, y, num; i < n; )
    {
        // 把当前列所有点都插入
        for(x = p[i].x; i < n && p[i].x == x; ++i)
        {
            y = lower_bound(dsc, dsc + ny, p[i].y) - dsc;
            update(y, 0, ny - 1, 1);
        }
        // 找第 n / 2 个纵坐标
        y = kth(n>>1, 0, ny - 1, 1);
        // 检查矩形里是否恰好有 n / 2 个点
        num = query(0, y, 0, ny - 1, 1);
        if(num << 1 == n)
            res = min(res, (dsc[y] + 1LL) * (x + 1LL));
    }
    return res;
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &m, &n))
    {
        for(int i = 0; i < n; ++i)
            scanf("%d%d", &p[i].x, &p[i].y);
        // 奇数直接忽略
        if(n & 1)
        {
            puts("-1");
            continue;
        }
        long long ans = (long long)m * m;
        for(int i = 0; i < 4; ++i)
        {
            ans = min(ans, solve(n, m));
            // 顺时针旋转矩阵
            rotate(n, m);
        }
        if(ans >= (long long)m * m)
            ans = -1;
        printf("%I64d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值