题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5738
题目大意:给定平面上的n个点,一个集合合法当且仅当集合中存在一对点u,v,对于集合中任意点w,均有dis(u,v)≥[dis(u,v)+dis(u,w)+dis(v,w)]/2。其中dis(A,B)为A,B两点的欧几里得距离。问你这样的合法集合有多少个。数据范围:1≤n≤1000。
解题思路:对于所给条件简单分析一下就能转化题意,实际上是在求有多少个集合满足集合内的点都在同一直线上。
比赛时的思路一直是,由于可能有重点,于是把重点整合到一个集合里,记一下集合里的个数cnt,那么对于这样一个重点的集合,从中任选至少两个都能作为合法点集,因此对答案的贡献是(2^cnt - cnt - 1)。下面考虑非重点,枚举重点集,对其他重点集按到此点集的距离从小到大排序,再依次枚举,用map记录一下斜率,就能在枚举过程中得到两个重点集之间的点个数K(也就是有多少个点也是这个斜率),对答案的贡献就是(2^cnt1 - 1)*(2^cnt2 - 1)*(2^K)。这样做思路应该没什么问题,只是常数比较大,代码难度也不小。一直到比赛结束也一直WA。
后来看了题解,思路比较清楚。对于这种数方案,要不重不漏的题目要记得使用有序化思想。而在二维坐标中,很常用的两种排序就是x,y双关键字排序,还有极角排序。
首先我们对这n个点做双关键字排序,然后对于x相同的点,个数为cnt,那么对答案的贡献就是(2^cnt - cnt - 1),然后对于每个重点集,对它右面的点做极角排序,做这个排序的意义在于以下就把同一斜率的点搞到了一起,方便计算了。假设当前极角上有p个点那么对答案的贡献就是(2^cnt - 1)*(2^p - 1)。
值得注意的也是十分重要的,就是极角排序的正确做法。首先计算dx, dy,并除掉abs(gcd(dx,dy)),一定要取绝对值!!不然会WA得不明所以!!然后cmp()的时候不要把斜率化成小数比较,直接dyA*dxB<dxA*dyB这样比较可避免精度问题。
总之这道题思想难度不是很大,但是需要的技巧比较多,细节也比较多,是值得一写的题目。
1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 using namespace std; 7 typedef long long LL; 8 9 const int MaxN = 1000, Pt = 1e9 + 7; 10 struct Point { 11 int x, y, dx, dy; 12 }a[MaxN + 5], b[MaxN + 5]; 13 int T, n; 14 LL Pow[MaxN + 5]; 15 LL ans; 16 17 int Gcd(int x, int y) 18 { 19 if (y == 0) return x; 20 return Gcd(y, x % y); 21 } 22 23 void Init() 24 { 25 scanf("%d", &n); 26 Pow[0] = 1; 27 for (int i = 1; i <= n; i++) { 28 scanf("%d%d", &a[i].x, &a[i].y); 29 Pow[i] = (Pow[i - 1] * 2) % Pt; 30 } 31 } 32 33 bool cmp(Point A, Point B) { 34 if (A.x == B.x) return A.y < B.y; 35 return A.x < B.x; 36 } 37 38 bool cmp2(Point A, Point B) { 39 return (LL)A.dy * B.dx < (LL)A.dx * B.dy; 40 } 41 42 void Solve() 43 { 44 ans = 0; 45 sort(a + 1, a + n + 1, cmp); 46 int L = 1, R = 1; 47 while (L <= n) { 48 while (R < n && a[R + 1].x == a[R].x) R++; 49 //printf("%d %d\n", L, R); 50 ans = (ans + Pow[R - L + 1] - 1 - (R - L + 1)) % Pt; 51 int l = L, r = L; 52 while (l <= R) { 53 while (r < R && a[r + 1].y == a[r].y) r++; 54 //printf("**%d %d\n", l, r); 55 int tot = 0; 56 for (int i = R + 1; i <= n; i++) { 57 b[++tot].dx = a[i].x - a[l].x; 58 b[tot].dy = a[i].y - a[l].y; 59 int D = Gcd(b[tot].dx, b[tot].dy); 60 if (D < 0) D = -D; 61 b[tot].dx /= D; b[tot].dy /= D; 62 } 63 sort(b + 1, b + tot + 1, cmp2); 64 int cnt = 1; 65 for (int i = 1; i <= tot; i++) { 66 if (i == tot || cmp2(b[i], b[i + 1])) { 67 ans = (ans + (Pow[r - l + 1] - 1) * (Pow[cnt] - 1)) % Pt; 68 cnt = 1; 69 }else cnt++; 70 } 71 l = r + 1; r = r + 1; 72 } 73 L = R + 1; R = R + 1; 74 } 75 printf("%I64d\n", ans); 76 } 77 78 int main() 79 { 80 scanf("%d", &T); 81 for (int i = 1; i <= T; i++) { 82 Init(); 83 Solve(); 84 } 85 }