省选模拟赛20200131 T3 数星星【三角形二维数点】

题目描述:

一个二维平面直角坐标系,其中有 N 颗星星(坐标为整点),你会有 M 个询问,询问以某个整 点为顶点的正三角形包含大于等于 K 个星星的最小非负整数边长为多少,如果无 法满足输出-1。
对于一个 正三角形,如果给出的顶点为(X,Y),边长为 L(L>=0,当 L 为 0 的时候退化为一 个点),那么三个顶点坐标分别为(X,Y),(X+L/2,Y+L/2*sqrt(3)),(X+L,Y)。
N , M ≤ 30000 , 0 ≤ X , Y ≤ 1 0 8 N,M\le30000,0\le X,Y\le10^8 N,M30000,0X,Y108

题目分析:

对于询问,显然可以二分答案,我们可以把所有二分一起进行,那么相当于需要进行 log 轮查询多个三角形里面各有多少个点

如果是查矩形,这很好做,扫描线树状数组就可以解决。
但是三角形?
在这里插入图片描述
于是膜拜了std。。

三角形可以这么表示:
在这里插入图片描述
(注意S2和S3射线的起点不在三角形上)

那么现在问题就变成了数一个夹角范围的点。
std给出的解法是这样的:
首先我们考虑能否数出在一条直线某一侧的点:
设我们询问的点为 ( A , B ) (A,B) (A,B),直线的斜率为 k ( k ≥ 0 ) k(k\ge0) k(k0),那么在直线右侧的点 ( x , y ) (x,y) (x,y)需要满足: k ∗ ( x − A ) + B ≥ y k*(x-A)+B\ge y k(xA)+By
转化一下变成: k x − y ≥ k A − B kx-y\ge kA-B kxykAB
也就是说我们将点 ( x , y ) (x,y) (x,y)改为 k x − y kx-y kxy,问题就变成了数轴上右边的点数。
如果是一个夹角范围 k 1 , k 2 ≥ 0 k_1,k_2\ge0 k1,k20,(根据 k k k的正负调整不等号的方向)
将点改为 ( k 1 x − y , k 2 x − y ) (k_1x-y,k_2x-y) (k1xy,k2xy)之后,就变成了矩形的二维数点问题!只需要数出 ( k 1 A − B , k 2 A − B ) (k_1A-B,k_2A-B) (k1AB,k2AB)右上角的点就可以了。

我自己yy了一个解法:
以形成夹角的两条射线的方向向量为基底改写点到原点的方向向量,这样就只需要统计每个询问点右上角的点了。

(这道题在算S2的时候由于起点不是整点可能会有一些精度问题。)

Code(std解法):

#include<bits/stdc++.h>
#define maxn 30005
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const double SQRT3 = sqrt(3), eps = 1e-10;
int n,m,k,L[maxn],R[maxn],len[maxn],id[maxn],S1[maxn],S2[maxn],S3[maxn],ans[maxn];
double Y[maxn<<1];int cy;
struct Point{
	double x,y; Point(){}
	Point(double x,double y):x(x),y(y){}
	bool operator < (const Point &p)const{return x<p.x;}
}p[maxn],p1[maxn],p2[maxn],p3[maxn],q[maxn],tq[maxn];
//awesome!!!
inline Point getp1(Point p){return Point(p.x-p.y/SQRT3,p.y);}
inline Point getp2(Point p){return Point(p.x-p.y/SQRT3,p.x+p.y/SQRT3);}
inline Point getp3(Point p){return Point(-p.y,p.x+p.y/SQRT3);}
//su ba ra sii!!!
int arr[maxn<<1];
inline void upd(int i){for(;i<=cy;i+=i&-i) arr[i]++;}
inline int qsum(int i){int s=0;for(;i;i-=i&-i) s+=arr[i];return s;}
bool cmp(int i,int j){return tq[i].x<tq[j].x;}
inline bool equal(double x,double y){return fabs(x-y)<eps;}
inline int getval(double x){
	int i=lower_bound(Y+1,Y+1+cy,x)-Y;
	return i<=cy&&equal(Y[i],x)?i:i-1;
}
void solve(Point *a,int *S,int flg){
	for(int i=1;i<=m;i++) id[i]=i,Y[i]=tq[i].y;
	sort(id+1,id+1+m,cmp),cy=m;
	for(int i=1;i<=n;i++) Y[++cy]=a[i].y;
	sort(Y+1,Y+1+cy);int tmpc=1;
	for(int i=2;i<=cy;i++) if(!equal(Y[i],Y[i-1])) Y[++tmpc]=Y[i];
	cy=tmpc,memset(arr,0,(cy+1)<<2);
	for(int o=m,j=n;o>=1;o--){
		int i=id[o]; if(flg==3) tq[i].x++;//!!!
		for(;j>=1&&a[j].x>=tq[i].x-eps;j--) upd(cy-getval(a[j].y)+1);
		int qy=getval(tq[i].y); if(flg>1) qy++;//!!!
		S[i]=qsum(cy-qy+1);
	}
}
int main()
{
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	read(n),read(m),read(k);
	for(int i=1;i<=n;i++) read(p[i].x),read(p[i].y),p1[i]=getp1(p[i]),p2[i]=getp2(p[i]),p3[i]=getp3(p[i]);
	for(int i=1;i<=m;i++) read(q[i].x),read(q[i].y),tq[i]=getp1(q[i]),R[i]=2e8,ans[i]=-1;
	sort(p1+1,p1+1+n),sort(p2+1,p2+1+n),sort(p3+1,p3+1+n);
	solve(p1,S1,1);
	for(int t=1;t<=30;t++){
		for(int i=1;i<=m;i++) len[i]=(L[i]+R[i])>>1;
		for(int i=1;i<=m;i++) {double l=len[i]*0.5;tq[i]=getp2(Point(q[i].x+l,q[i].y+l*SQRT3));}
		solve(p2,S2,2);
		for(int i=1;i<=m;i++) tq[i]=getp3(Point(q[i].x+len[i],q[i].y));
		solve(p3,S3,3);
		for(int i=1;i<=m;i++)
			if(S1[i]-S2[i]+S3[i]>=k) ans[i]=R[i]=len[i];
			else if(L[i]<R[i]) L[i]=len[i]+1;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值