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

真的简单好多好多,我傻了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值