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;
}