赶牛入圈

赶牛入圈

有一个\(10000\times 10000\)的网格图,给出n个棋子在网格上的坐标,记第i个棋子的坐标为\((x_i,y_i)\),现在请求出一个边长最小的正方形(边对齐网格),让其中包含的棋子数大于等于c,\(n\leq 500\)

注意到数字范围很大,数据范围很小,考虑离散化(提一下,网格坐标很小,于是可以桶排排序)。

问题在于离散化后,在离散化后的网格图中我们如何枚举一个正方形,常规思路是确定一个矩形的左上角,再确定右下角,然后对矩形的原始长宽取max就是我们要的正方形边长了,显然时间复杂度太高\(O(n^3)\)

注意到正方形的顶点都可以是由一个棋子的x轴坐标所确定的直线和另外一个棋子的y轴坐标所确定的直线的交点组成的(不是的也可以平移成如此),我们只要枚举离散化后的x轴坐标和y轴的坐标,即可以确定这个点,然后只要确定对角顶点即可。

但是注意到这个点不一定存在,于是查询出正方形边界的最近的离散化坐标,然后查询这个区间的二维前缀和即可(注意此时的矩形边界上的棋子数要舍区),于是我们就可以在\(O(n^2log(n))\)解决这道题目。

参考代码:

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define intmax 0x7fffffff
using namespace std;
struct pos{
	int x,y;
}p[550];
int x[10005],y[10005],M[550][550],
	xl[550],xt,yl[5501],yt,n,c;
il void read(int&);il int check(int);
int main(){
	read(c),read(n);
	for(int i(1);i<=n;++i)
		read(p[i].x),read(p[i].y),
			++x[p[i].x],++y[p[i].y];
	for(int i(1);i<=10000;++i){
		if(x[i])xl[++xt]=i;x[i]=xt;
		if(y[i])yl[++yt]=i;y[i]=yt;
	}for(int i(1);i<=n;++i)++M[x[p[i].x]][y[p[i].y]];
	for(int i(1),j;i<=500;++i)
		for(j=1;j<=500;++j)
			M[i][j]+=M[i-1][j]+M[i][j-1]-M[i-1][j-1];
	int l(1),r(10000),mid;
	while(l<=r){mid=l+r>>1;
		if(check(mid))l=mid+1;
		else r=mid-1;
	}printf("%d",l);
	return 0;
}
il int check(int mid){
	for(int i(x[mid]),j,k,l;i<=xt;++i)
		for(j=y[mid];j<=yt;++j){k=l=0;
			if(xl[i]-mid>=0)k=x[xl[i]-mid];
			if(yl[j]-mid>=0)l=y[yl[j]-mid];
			if(M[i][j]-M[i][l]-M[k][j]+M[k][l]>=c)return false;
		}return true;
}
il void read(int &x){
	x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

posted @ 2019-07-22 18:52  a1b3c7d9  阅读(241)  评论(0编辑  收藏  举报