2016 Multi-University Training Contest 2 - 1005 (hdu5738)

题目链接: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 }

 

转载于:https://www.cnblogs.com/ChopsticksAN/p/5693442.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值