HDU 6731 - Angle Beats
Angle Beats
Time Limit: 20000/15000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 318 Accepted Submission(s): 43
Problem Description
Given n points P1, P2, … , Pn on 2D plane and q queries. In i-th query, a point Ai is given, and you should determine the number of tuples (u, v) that 1 ≤ u < v ≤ n and Ai , Pu, Pv form a non-degenerate right-angled triangle.
Input
The first line contains two positive integers n, q (2 ≤ n ≤ 2 000, 1 ≤ q ≤ 2 000), denoting the numberof given points and the number of queries.
Next n lines each contains two integers xi , yi (|xi|, |yi| ≤ 109), denoting a given point Pi.
Next q lines each contains two integers xi , yi (|xi|, |yi| ≤ 109), denoting a query point Ai.
It is guaranteed that the input n + q points are all pairwise distinct.
Output
Output q lines each contains a non-negative integer, denoting the answer to corresponding query.
Sample Input
4 2
0 1
1 0
0 -1
-1 0
0 0
1 1
Sample Output
4
3
Hint
For query (0, 0), the 4 right-angled triangles are
� {(0, 0),(0, 1),(1, 0)}
� {(0, 0),(0, 1),(-1, 0)}
� {(0, 0),(0,-1),(1, 0)}
� {(0, 0),(0,-1),(-1, 0)}
For query (1, 1), the 3 right-angled triangles are
� {(1, 1),(0, 1),(1, 0)}
� {(1, 1),(0, 1),(0,-1)}
� {(1, 1),(1, 0),(-1, 0)}
在平面上给定 n 个互不相同的点,有 q 次询问。
每次询问给定一个点 A,问这 n 个点中有多少个无序点对 (B,C),使得 A,B,C 可以构成一个非退化的直角三角形。
如果 A 是直角顶点,那么就只需要对 n 个点关于 A 做一个极角排序,然后统计有多少个点对 (B, C),使得 AB 垂直 AC。
如果 A 不是直角顶点,考虑离线做法。先把所有的询问点读进来,然后枚举 n 个给定点,对于每个给定点 B,给其他n + q[ 1 个点关于 B 进行极角排序并按极角序扫描所有点,每扫到一个询问点就统计垂直方向上的给定点的个数并把答案累加
到当前询问。
时间复杂度:O(qn log n + n(n + q)log(n + q))。
作为蒟蒻卡在了求有多少个点对 (B, C),使得 AB 垂直 AC上。左思右想相处了一个跑了8s的代码,听说有人3s就跑完了。膜一下。
想法是把以
A
i
A_i
Ai为起点的向量极角排序,得到数组
t
t
t,然后
t
[
j
]
t[j]
t[j]指向当前向量,
t
[
k
]
t[k]
t[k]指向与
t
[
j
]
t[j]
t[j]垂直的向量(
t
[
k
]
t[k]
t[k]是
t
[
j
]
t[j]
t[j]逆时针方向上的,我们只考虑逆时针方向上的垂直的向量,因为我们会遍历所有的向量,所以如果
t
[
j
]
t[j]
t[j]在顺时针方向上有垂直的向量,在后面也会遍历到),遇到一组就ans[i]++。这样子只要把
n
n
n个向量都跑两遍(
j
j
j一遍
k
k
k一遍)左右就差不多了。然而实际操作起来有许许多多复杂的小细节。
贴代码讲解吧
(更新:我裂开了,可以直接用lower_bound找垂直的向量,nlog(n)就可以了,手写一个O(n)的也就快了4s,代码难度还炸裂。不说了,我裂开了,吐血。代码贴在最后)
顺便先贴一个极角排序的cmp
int qua(point a)
{
if(a.x>0 && a.y>=0) return 1;
if(a.x<=0 && a.y>0) return 2;
if(a.x<0 && a.y<=0) return 3;
if(a.x>=0 && a.y<0) return 4;
}
bool cmp1(const point &a,const point &b)
{
return cross(a,b) > 0;//叉积
}
bool cmp(const point &a,const point &b) //先按象限从小到大排序 再按极角从小到大排序
{
if(qua(a)==qua(b))//返回值就是象限
return cmp1(a,b);
else return qua(a)<qua(b);
}
for (int j = 1,k = 2,st = 2;j<=n;j++)
//j表示当前向量,k表示与j垂直的向量,st表示k每次从哪个向量开始找
{
k = st;
if (k == j) st = k = k + 1 > n?1:k+1;
//如果j和k向量重合了,k就从j的下一个向量开始找同时st也要更新到j的下一个向量
while (k != j)//如果找了一圈都没找到
{
if (t[j] * t[k] > 0 && cross(t[j],t[k]) >= 0) st = k = k + 1 > n?1:k+1;
//这里*是点积,如果t[j]和有t[k]相差小于90°,且t[k]在t[j]的逆时针方向上
//因为j的下一个向量肯定比j现在这个向量距离k更近(或相等)
//所以下次枚举遍历的时候要从更远的向量开始
//用st来记录与k相差小于等于90且t[k]在t[j]的逆时针方向的距离j最远的向量
//下次寻找与j相等的向量就从st开始可以节省时间
// cross(t[j],t[k]) >= 0中>=0是为了使得t[j]与t[k]重合时,k也会枚举到下一个向量
else if (t[j] * t[k] == 0 && cross(t[j],t[k]) > 0) ans[i]++,k = k + 1 > n?1:k+1;
//如果t[j]和有t[k]相差等于90°,且t[k]在t[j]的逆时针方向上
//这里st不更新是为了t[j+1]和t[j]重合时,k从st开始,t[j+1]也可以累计答案
else if (t[j] * t[k] < 0 || cross(t[j],t[k]) < 0) break;
//如果t[j]和t[k]相差大于90°或者t[k]在t[j]的顺时针方向上
}
}
第二部分的循环和第一部分基本一样,就是如果是 A i A_i Ai里的点就用inedx记录下是第 i i i个,然后如果存在垂直的情况就 a n s [ i ] + + ans[i]++ ans[i]++
for (int j = 1;j<=n;j++)
if (i != j) t[++len] = a[j] - a[i];
for (int j = 1;j<=q;j++)
{
t[++len] = node[j] - a[i];
t[len].index = j;
}
sort(t+1,t+len+1,cmp);
for (int j = 1,k = 2,st = 2;j<=len;j++)
{
k = st;
if (k == j) st = k = k + 1 > len?1:k+1;
while (k != j)
{
if (t[j] * t[k] > 0 && cross(t[j],t[k]) >= 0) st = k = k + 1 > len?1:k+1;
else if (t[j] * t[k] == 0 && cross(t[j],t[k]) > 0)
{
if (t[j].index != 0 && t[k].index == 0) ans[t[j].index]++;
else if (t[j].index == 0 && t[k].index != 0) ans[t[k].index]++;
k = k + 1 > len?1:k+1;
}
else if (t[j] * t[k] < 0 || cross(t[j],t[k]) < 0) break;
}
}
除了样例外再给一组数据
6 2
1 0
2 0
0 2
-1 0
0 -2
-1 -1
2 2
1 1
输出
5
7
贴上lower_boung的代码(orz)
for (int j = 1;j<=n;j++)
{
point v;
v.x = -t[j].y; v.y = t[j].x;
int k = lower_bound(t+1,t+n+1,v,cmp) - t;
while (t[j] * t[k] == 0 && cross(t[j],t[k]) > 0) k = k + 1>n?1:k+1,ans[i]++;
}
for (int i = 1;i<=n;i++)
{
point t[4010] = {};
int len = 0;
for (int j = 1;j<=n;j++)
if (i != j) t[++len] = a[j] - a[i];
for (int j = 1;j<=q;j++)
{
t[++len] = node[j] - a[i];
t[len].index = j;
}
sort(t+1,t+len+1,cmp);
for (int j = 1;j<=len;j++)
{
point v;
v.x = -t[j].y; v.y = t[j].x;
int k = lower_bound(t+1,t+len+1,v,cmp) - t;
while (t[j] * t[k] == 0 && cross(t[j],t[k]) > 0)
{
if (t[j].index != 0 && t[k].index == 0) ans[t[j].index]++;
else if (t[j].index == 0 && t[k].index != 0) ans[t[k].index]++;
k = k + 1>len?1:k+1;
}
}
}
真的简单好多好多,我傻了。
本文来自博客园,作者:Un-Defined,转载请保留本文署名Un-Defined,并在文章顶部注明原文链接:https://www.cnblogs.com/EIPsilly/p/17463694.html