C1005 进行一个三角的查询题解
-
题目描述
平面上有 个点 。
现在有 个询问,每次给定三个点 ,回答有多少个点 在这个三角形的边界或者内部。
输入格式
第一行,两个整数 。
接下来 行,每行两个整数 ,表示点的坐标。
接下来 行,每行三个整数 ,表示一个询问。
输出格式
输出 行,每行一个整数,表示答案。
40pts
直接暴力即可。由于题目时限有 10s,直接 枚举,能够获得 分。
我下面程序使用二分先直接锁定到了三角形的 范围,并没有改善理论复杂度,甚至理论复杂度多了一个 ,但在测试中能把程序速度优化 8s 左右。和直接 枚举分数一样,均为 分。
#include <iostream> #include <cstdio> #include <algorithm> #define INF 0x7fffffff #define MAXN 1000005 #define eps 1e-9 #define foru(a,b,c) for(int a=b;a<=c;a++) #define RT return 0; #define LL long long #define LXF int #define RIN read_32() #define HH printf("\n") #define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++) using namespace std; char buf[1<<20],*p1,*p2; inline int read_32(){LXF X=0,w=0;char ch=0;while(ch<'0'||ch>'9'){w|=ch=='-';ch=GC;}while(ch>='0'&&ch<='9') X=(X<<3)+(X<<1)+(ch^48),ch=GC;return w?-X:X;} int n,q; class Pos{ public: int x,y; bool operator < (const Pos _x)const{ return this->x<_x.x; } Pos(int _x=0,int _y=0):x(_x),y(_y){ } }a[MAXN]; signed main(){ n=RIN,q=RIN; int x,y,d,ans; for(int i=1;i<=n;i++){ x=RIN,y=RIN; a[i]={x,y}; } sort(a+1,a+1+n); for(int i=1;i<=q;i++){ x=RIN,y=RIN,d=RIN,ans=0; int bg=lower_bound(a+1,a+1+n,Pos(x,y))-a; int ed=upper_bound(a+1,a+1+n,Pos(x+d,y))-a; for(int j=bg;j<=ed;j++){ if(a[j].y>=y && a[j].y<=-a[j].x+x+y+d){ ans++; } } printf("%d\n",ans); } return 0; }
100pts
我们先来画个图,把询问的三角形画出来。
我们就是要询问在这个三角形内的点的数量对吧?
把它补成这样一个梯形,那么就会有:
又因为 可以表示成这个:
所以:
那我们该怎么做呢?我们把这四“项”分两次解决,第一次解决,第二次解决 。
观察 和 ,他们俩都是个梯形,而且斜着的那条边所在的直线相同。
观察 和 ,他们俩都是个矩形,而且顶部的那条边所在的直线相同。
这就可以扫一遍解决了。
具体地,为了解决前两项,我们先把所有点按照以下规则排序:
按照 "过这个点的,斜率为 的直线与 轴交点的纵坐标大小" 升序。
比较抽象?其实很简单,画个图就懂了。
排序完就是按照绿色数字顺序访问这些点的。
这样有什么好处?比如我们访问到第四个点 (与原题的字母标号无关)的时候,发现它斜下方的点都已经访问过了。换句话说,如果我问你 斜下方有几个点,你能很轻松的回答出来,前面的都是嘛!
这里说的“斜下”其实不严谨,应该是 所在的斜率为 直线下方的点。即直线 下方的点。说"斜下"只是为了直观理解。
但我们刚才分析出来的四块面积里没有三角形啊?这是干嘛呢?我们不是想要梯形吗?
我们刚才之所以只能回答在斜下方的三角形里有多少点,是因为我们只是开了一个变量来记录。我们开个桶不就行了?
我们模仿树状数组求逆序对时的思路,在值域上开个树状数组。访问到一个点的时候,我们就把它的横坐标的地方在桶里 ,这样我们用树状数组查询前缀和的时候,就可以查询任何位置左侧点的数量了。
比如在上图中,我们已经访问到了 ,那么 都应该已经插入了树状数组。这时候我们 ,意思就是获取横坐标小于等于 的点的数量,自然就知道是 了。
发现什么?这不就截出来梯形了吗?
OK,我们马上就要解决查询梯形的问题了。我们到目前为止只是对所有的点排了序,我又不是问点,我是问三角形,咋办?
还是这张图:
虽然 这个三角形是个询问,但我们可以把这个询问伪装成一个点,和题目中的点混在一起。相当于,我们随便选了 中的一个点,之所以可以随便选是因为他们都在 上,然后把他混进输入的点中排序,但得给它留一个“我其实是询问”的标记。
以选 来伪装为例,当我们访问到 时,直线 下方的所有点已经被插入了树状数组,我们只需要 就能得到 , 就能得到。
但这样发现很难实现。我们把 自己混进去了,倒时候去哪里找 呢?
所以我们换种“伪装”,大体思想是一样的。我们把所有点 ,看成是一组 的询问,然后把他们全混一起。排序的时候,不能按照 排序,而是按照 排序。具体这个式子是怎么来的,请读者自行把点坐标带入一次函数解析式来理解。
这样,对于所有的点,排序的时候凭据是过它的那条斜率为 的直线,而对于所有的询问,我们在保留所有信息的同时,以它的斜边所在的直线作为排序凭据。
注意排序会打乱顺序,所以我们要记录原本的序号 ,方便我们输出。这也正好可以用来在遍历时辨别到底访问的是个点还是个询问。
这部分代码+读入部分代码:( 是树状数组,比如修改就是 , 是答案数组, 是快读, 存询问)
bool cmpB(Pos x,Pos y){
int b1=x.x+x.y+x.d;
int b2=y.x+y.y+y.d;
return b1<b2 || (b1==b2 && x.id<y.id);
}
int ans[MAXN];
signed main(){
n=RIN,q=RIN;
int x,y,d;
for(int i=1;i<=n;i++){
x=RIN,y=RIN;
a[i]=Pos(x,y,0,0);
}
for(int i=1;i<=q;i++){
x=RIN,y=RIN,d=RIN;
a[i+n]=Pos(x,y,d,i);
}
sort(a+1,a+1+n+q,cmpB);
for(int i=1;i<=n+q;i++){
if(a[i].id==0) c.add(a[i].x,1);
else{
ans[a[i].id]+=c.ask(a[i].x+a[i].d)-c.ask(a[i].x-1);//加上大矩形,减去CF"左侧"的面积。
}
}
......
}
如果问为什么有个 ,是因为树状数组返回的是前缀和,是小于等于的数量。可结合前缀和的 来理解,一个道理。如果后一个ask不减1,所有在直线CF上的、理应算在三角形的点也会被错误地删去。
下面来解决那俩矩形的问题。即 和 。甚至更简单了,我们只需要把所有混起来的点和询问按照纵坐标排序即可。这样访问到某个点/询问时,其下方的点已经全部被插入树状数组。
直接给出这部分+输出的代码:
bool cmpY(Pos x,Pos y){
return x.y<y.y || (x.y==y.y && x.id>y.id);
}
signed main(){
.....
sort(a+1,a+1+n+q,cmpY);
for(int i=1;i<=n+q;i++){
if(a[i].id==0) c.add(a[i].x,1);
else{
ans[a[i].id]-=c.ask(a[i].x+a[i].d)-c.ask(a[i].x-1);
}
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
}
有个细节, 里写的是 。这是为什么呢?
我们是钦定所有点的 为 的,这个语句的意义也就是:当纵坐标相同时,优先访问完所有这个高度的询问,再访问这个高度的点。
这与先访问点再访问询问有何不同呢?如果我们先访问点,那么在访问相同高度的询问时,与询问同高的点也被加入了树状数组。这就会导致错误。
比如 是个询问。我们假设 上有某个题目输入的点 ,如果我们先访问相同高度的点,即先访问点 ,那我们在处理询问 的时候,,会把 也算进 扣除掉,这显然不是我们想要的, 上的点是要计入答案的,怎么能被扣除掉呢?
这一块代码中也出现了 ,道理和刚才是一样的,我们是想要 “ 左侧” 点的数量,而不是 ” 上 + 左侧“ 点的数量。
至此整个题目结束。由于 同级,复杂度是 的。
注意树状数组值域要开到 ,因为有可能会 ,这两个都是 级别的。
完整代码:
#include <bits/stdc++.h>
#define INF 0x7fffffff
#define MAXN 1000005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN read_32()
#define HH printf("\n")
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
using namespace std;
char buf[1<<20],*p1,*p2;
inline int read_32(){LXF X=0,w=0;char ch=0;while(ch<'0'||ch>'9'){w|=ch=='-';ch=GC;}while(ch>='0'&&ch<='9') X=(X<<3)+(X<<1)+(ch^48),ch=GC;return w?-X:X;}
int n,q;
class Pos{
public:
int x,y,d,id;
Pos(int _x=0,int _y=0,int _d=0,int _id=0):x(_x),y(_y),d(_d),id(_id){
}
}a[MAXN<<1];
inline int lb(int x){return x&-x;}
class Tree{
private:
int c[MAXN<<1];
public:
void add(int x,int k){
for(;x<=2000000;x+=lb(x)){
c[x]+=k;
}
}
int ask(int x){
int ret=0;
for(;x;x-=lb(x)){
ret+=c[x];
}
return ret;
}
void clear(){
memset(c,0,sizeof c);
}
}c;
bool cmpY(Pos x,Pos y){
return x.y<y.y || (x.y==y.y && x.id>y.id);
}
bool cmpB(Pos x,Pos y){
int b1=x.x+x.y+x.d;
int b2=y.x+y.y+y.d;
return b1<b2 || (b1==b2 && x.id<y.id);
}
int ans[MAXN];
signed main(){
n=RIN,q=RIN;
int x,y,d;
for(int i=1;i<=n;i++){
x=RIN,y=RIN;
a[i]=Pos(x,y,0,0);
}
for(int i=1;i<=q;i++){
x=RIN,y=RIN,d=RIN;
a[i+n]=Pos(x,y,d,i);
}
sort(a+1,a+1+n+q,cmpB);
for(int i=1;i<=n+q;i++){
if(a[i].id==0){
c.add(a[i].x,1);
}else{
ans[a[i].id]=ans[a[i].id]+c.ask(a[i].x+a[i].d)-c.ask(a[i].x-1);//+大梯形-小梯形
}
}
c.clear();//具体实现见class,就是个memset。
sort(a+1,a+1+n+q,cmpY);
for(int i=1;i<=n+q;i++){
if(a[i].id==0){
c.add(a[i].x,1);
}else{
ans[a[i].id]=ans[a[i].id]-c.ask(a[i].x+a[i].d)+c.ask(a[i].x-1);//-大矩形+小矩形
}
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步