题解:CF1019D Large Triangle

题意

给定 n n n 个平面上的点,求是否存在 3 3 3 个点使得它们组成的三角形面积为 S S S。需要输出三个点的坐标。

n ≤ 2000 n\le2000 n2000

解法

暴力做法:枚举 3 3 3 个点,海伦公式判断面积是否相等。复杂度 O ( n 3 ) O(n^3) O(n3)。优化思路即为:对于先枚举的两个点 A , B A,B A,B 组成的连线 l l l,那么第 3 3 3 个点 C C C 就要满足其到这条连线的距离为 2 m ∣ l ∣ \cfrac{2m}{|l|} l2m。如果其余点与 l l l 的距离有序,那么就可以 O ( log ⁡ n ) O(\log n) O(logn) 的复杂度二分了,分成在 l l l 之上、之下两个部分。

考虑将所有点进行旋转,使 l l l y y y 轴重合。这时候可以发现一个性质:

  • l l l 为纵轴时,对于任意两个点 X , Y X,Y X,Y,它们的横坐标大小关系,只与原图中 l l l A B AB AB 的斜率有关。

然后做法就出来了:先把 n 2 n^2 n2 条连线按照斜率从小到大排序,然后从小到大枚举,这样任意两点旋转后的横坐标大小关系恰好会变化一次。枚举连线的过程中每次交换两个端点,再在连线的两侧(正负)进行查找即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2005;
#define ll long long
int n, a[maxn + 5];
ll m;
struct Point { 
    int id, x, y;
} p[maxn + 5];
struct Segment {
    int x, y;
    double k;
    bool operator< (const Segment &o) const { // 按照斜率
        return k < o.k; 
    }
} sid[maxn * maxn + 5];
bool cmp(const Point &A, const Point &B) { 
    return A.x < B.x; 
}
ll cal(const Point &u,const Point &v,const Point &w) { // 算出第 3 条边的长度
    return abs(1ll * (u.x - v.x) * (u.y - w.y) - 1ll * (u.x - w.x) * (u.y - v.y));
}
bool ok = false;
// 对于 l 进行左右两侧的二分。
void F1(int l, int r, const Point &A, const Point &B) {  
    while (l <= r) {
        int mid = l + r >> 1; 
        ll S = cal(p[mid], A, B);
        if (S == 2 * m) {
            printf("Yes\n%d %d\n%d %d\n%d %d\n", A.x, A.y, B.x, B.y, p[mid].x, p[mid].y);
            ok = true; return ;
        } else if (S < 2 * m) r = mid - 1;
        else l = mid + 1;
    }
}
void F2(int l, int r, const Point &A, const Point &B) {
    while(l <= r) {
        int mid = l + r >> 1; 
        ll S = cal(p[mid], A, B);
        if (S == 2 * m) {
            printf("Yes\n%d %d\n%d %d\n%d %d\n", A.x, A.y, B.x, B.y, p[mid].x, p[mid].y); 
            ok = true; return ;
        } else if (S < 2 * m) l = mid + 1;
        else r = mid - 1;
    }
}
int tot = 0;
int main() {
    scanf("%d%lld", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%d%d", &p[i].x, &p[i].y);
    sort(p + 1, p + n + 1, cmp);
    for (int i = 1; i <= n; i ++)
        a[i] = p[i].id = i;
    for (int i = 1; i <= n; i ++)
        for (int j = i + 1; j <= n; ++j)
            sid[++ tot] = Segment{i, j, 1.0 * (p[j].y - p[i].y) / (p[j].x - p[i].x)};
    sort(sid + 1, sid + tot + 1); 
    for (int i = 1; i <= tot; i ++) {
        int mn = min(a[sid[i].x], a[sid[i].y]);
        int mx = max(a[sid[i].x], a[sid[i].y]); 
        swap(p[mn], p[mx]); swap(a[sid[i].x], a[sid[i].y]);
        F1(1, mn - 1, p[a[sid[i].x]], p[a[sid[i].y]]);
        if (ok) return 0;
        F2(mx + 1, n, p[a[sid[i].x]], p[a[sid[i].y]]);
        if (ok) return 0;
    }
    return puts("No"), 0;
}
  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值