D. Looking for Owls
Emperor Palpatine loves owls very much. The emperor has some blueprints with the new Death Star, the blueprints contain n distinct segments and m distinct circles. We will consider the segments indexed from 1 to n in some way and the circles — indexed from 1 to m in some way.
Palpatine defines an owl as a set of a pair of distinct circles (i, j) (i < j) and one segment k, such that:
- circles i and j are symmetrical relatively to the straight line containing segment k;
- circles i and j don‘t have any common points;
- circles i and j have the same radius;
- segment k intersects the segment that connects the centers of circles i and j.
Help Palpatine, count the number of distinct owls on the picture.
Input
The first line contains two integers — n and m (1 ≤ n ≤ 3·105, 2 ≤ m ≤ 1500).
The next n lines contain four integers each, x1, y1, x2, y2 — the coordinates of the two endpoints of the segment. It‘s guaranteed that each segment has positive length.
The next m lines contain three integers each, xi, yi, ri — the coordinates of the center and the radius of the i-th circle. All coordinates are integers of at most 104 in their absolute value. The radius is a positive integer of at most 104.
It is guaranteed that all segments and all circles are dictinct.
Output
Print a single number — the answer to the problem.
Please, do not use the %lld specifier to output 64-bit integers is С++. It is preferred to use the cout stream or the %I64d specifier.
Sample test(s)
Input
1 23 2 3 -20 0 26 0 2
Output
1
Input
3 20 0 0 10 -1 0 10 -1 0 02 0 1-2 0 1
Output
3
Input
1 2-1 0 1 0-100 0 1100 0 1
Output
0
Note
Here‘s an owl from the first sample. The owl is sitting and waiting for you to count it.
题意:有n条线段和m个圆(1 ≤ n ≤ 3·105, 2 ≤ m ≤ 1500),如果存在两个圆和这两个圆的对称线段(并且线段要和圆心的连线相交),那么一起称为一个owl,求一共有多少个owl。
思路:最暴力的做法就是枚举每一对圆,求它们的对称线,然后枚举每一条线段,判断线段是否在对称线上,复杂度o(m2n),但是数据规模太大无法承受,这题不是一道纯粹的几何题。
后来我想,如果在暴力的基础上改进一下,先预处理出每对圆的对称线,在枚举每一条线段的时候,如果能在o(1)或o(logn)内判断出这条对称线是否存在,应该能过。
于是我想到了将一条直线hash成一个long long值,这种hash还是第一次写,我将这条直线对应的向量通过求gcd将x,y都缩到最小,并且保证x非负,然后在这条直线上取一点使得x非负并且尽量小,将这向量和这点的坐标值随便乘一些很大的数再加起来就算hash了,这样就可以保证同一条直线不管已知哪两点,算出的hash值都唯一。
现在问题来了,怎么判断线段和圆心连线相交呢?如果再找出这两个圆心的话,那跟暴力无异。于是我又想到了将一个对称线的hash值和对称线和圆心的交点的x坐标弄成一个pair插入到一个vector中,全部完了后对vector排序,hash值小的在前,相等的话x坐标小的在前面,然后枚举每一条线段,将两端点和对应的向量也都hash一下然后弄成pair,对应用lower_bound和upper_bound二分查找,在这个区间内的pair肯定都是符合的,ans+=区间长度就可以了。
于是时间复杂度降到了o(m2+nlogm2)。
1 #include <iostream> 2 #include <stdio.h> 3 #include <map> 4 #include <cmath> 5 #include <vector> 6 #include <algorithm> 7 using namespace std; 8 #define TR(x) (x+10000)*2 9 typedef long long ll; 10 11 struct Point 12 { 13 int x, y; 14 Point(int x=0, int y=0):x(x),y(y) { } 15 }; 16 typedef Point Vector; 17 18 Vector operator - (const Point& A, const Point& B) 19 { 20 return Vector(A.x-B.x, A.y-B.y); 21 } 22 23 struct Circle 24 { 25 int x,y,r; 26 }; 27 28 struct Line 29 { 30 Point begin; 31 Point end; 32 }; 33 34 int gcd(int a,int b) 35 { 36 return b==0 ? a : gcd(b, a%b); 37 } 38 39 Vector simpleVector(Vector v) 40 { 41 if(v.x==0) 42 return Vector(0,1); 43 if(v.y==0) 44 return Vector(1,0); 45 int g=gcd(abs(v.x), abs(v.y)); 46 if(v.x>0) 47 return Vector(v.x/g, v.y/g); 48 return Vector(-v.x/g, -v.y/g); 49 } 50 51 Point simpleCenter(Point p, Vector v) // v.x>=0 && p.x>=0 && p.y>=0 52 { 53 if(v.x==0) 54 return Point(p.x,0); 55 if(v.y==0) 56 return Point(0,p.y); 57 Point r; 58 r.x = p.x % v.x; 59 int k = (p.x-r.x)/v.x; 60 r.y=p.y-k*v.y; 61 return r; 62 } 63 64 65 ll hash(Point p, Vector v) 66 { 67 ll ans= (ll)p.x*80001LL 68 +(ll)p.y*80001LL*80001 69 +(ll)v.x*80001LL*80001*40007 70 +(ll)v.y*80001LL*80001*40007*40007; 71 return ans; 72 } 73 74 typedef pair<ll, int> Pair; 75 vector<Pair> a; 76 77 Circle cs[1510]; 78 Line ls[300010]; 79 int main() 80 { 81 // 坐标+10000,再放大2倍,坐标范围[0,40000],且都是偶数 82 freopen("in.txt","r",stdin); 83 int nLine, nCircle; 84 cin>> nLine>> nCircle; 85 for(int i=0; i<nLine; i++) 86 { 87 int x1,y1,x2,y2; 88 scanf("%d %d %d %d",&x1,&y1,&x2,&y2); 89 ls[i]=Line {Point(TR(x1),TR(y1)),Point(TR(x2),TR(y2))}; 90 } 91 92 for(int i=0; i<nCircle; i++) 93 { 94 int x,y,r; 95 scanf("%d %d %d", &x, &y, &r); 96 cs[i]=Circle {TR(x),TR(y),r*2}; 97 } 98 99 for(int i=0; i<nCircle; i++) 100 for(int j=i+1; j<nCircle; j++) 101 if(cs[i].r == cs[j].r) 102 { 103 Vector c1c2=Vector(cs[j].x-cs[i].x, cs[j].y-cs[i].y); 104 // 判断两个圆是否相交 105 if(c1c2.x*c1c2.x+c1c2.y*c1c2.y <= 4*cs[i].r*cs[i].r) 106 continue; 107 Vector v=Vector(-cs[j].y+cs[i].y, cs[j].x-cs[i].x); // 垂直向量 108 v=simpleVector(v); // v.x>=0 109 110 Point center=Point((cs[j].x+cs[i].x)/2, (cs[j].y+cs[i].y)/2); 111 center = simpleCenter(center, v); 112 113 a.push_back(Pair(hash(center, v), (cs[j].x+cs[i].x)/2)); 114 } 115 116 sort(a.begin(), a.end()); 117 118 int ans=0; 119 for(int i=0; i<nLine; i++) 120 { 121 Vector v=simpleVector(ls[i].begin-ls[i].end); 122 Point p=simpleCenter(ls[i].begin,v); 123 ll h = hash(p,v); 124 125 // 在vector内二分查找在区间[L,R]内的的个数 126 Pair L = Pair(h, min(ls[i].begin.x, ls[i].end.x)); 127 Pair R = Pair(h, max(ls[i].begin.x, ls[i].end.x)); 128 vector<Pair>::iterator i1 = lower_bound(a.begin(), a.end(), L); 129 vector<Pair>::iterator i2 = upper_bound(a.begin(), a.end(), R); 130 ans+=i2-i1; 131 } 132 133 cout<< ans; 134 135 return 0; 136 }