The 18th Northeast Collegiate Programming Contest M-House

这题考的计算几何知识难度不大,只需要会求叉积和判垂直以及判断点是否在直线上即可。题目的数据范围很小(n\leq 300),暴力枚举五个点的时间复杂度是肯定会T的,需要优化到O(n^3)即可通过这个题。问题就变成了通过维护一个什么样的数据结构来降低枚举的时间复杂度,一开始的想法是把所有的线段先算出来,然后从中任取一条线段,通过线段的两个端点作垂线,判断是否有其他的点在这两条垂线上,如果存在就算出他到线段的距离,每一对距离相等的点相连构成的新线段就能和选出的线段组成一个矩形,再去求线段的中垂线,看有多少个点位于中垂线上。

但是仔细想想这样子的时间复杂度是不合理的,线段的数量是n^2级别的,所有时间复杂度应该是O(n^4)会T。而且距离一条线段距离相等的两个点不一定是在线段的同一侧,所以合理的解法是选择一条线段,枚举他两侧的点,一侧的点用来构成矩形,另外一侧的点用来做房子的顶点。这个做法好像是和官方的题解很像,但是感觉写起来会有点麻烦。

我的写法是队友给的一个思路,用向量来表示一个矩形的两条平行边,可以发现这两个向量是一样的。所以可以把开个map把所有相同的向量存在一起,去枚举两条相同向量对应的线段,然后取出四个端点(建议按照题目中给的图的顺序来命名端点,不容易把端点弄混)。假设我们现在取出的线段\overrightarrow{AB}\overrightarrow{CD},我们只需要判断\angle DAB是否为直角就能判断出这两条线段是否可以构成一个矩形。再去枚举点E,按题目中给的条件如果\overrightarrow{DA}\cdot \overrightarrow{DE}< 0,那么就可以构成一个房子。按照这种枚举方法我们枚举了\overrightarrow{AB}\overrightarrow{CD}E可以构成房子,还会枚举到了\overrightarrow{CD}\overrightarrow{AB}E构成房子,也就是每个房子将会被计算两次,需要将最终答案除2。

#include<bits/stdc++.h>
#define pb push_back
#define fixed(s) fixed<<setprecision(2)<<s

using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<db, db> PDD;

const int N = 310;

const db EPS = 1e-9;
int sign(db a){ return a < -EPS ? -1 : a > EPS; }
int cmp(db a, db b){ return sign(a - b); }

struct Point{
	db x, y;
	//int id;
    Point(db x_ = 0, db y_ = 0) : x(x_), y(y_) {}
};
struct Line {
    Point a;
    Point b;
    Line(Point a_ = Point(), Point b_ = Point()) : a(a_), b(b_) {}
};
Point operator+ (Point a, Point b){ return {a.x + b.x, a.y + b.y}; }
Point operator- (Point a, Point b){ return {a.x - b.x, a.y - b.y}; }
Point operator* (Point a, db b){ return {a.x * b, a.y * b}; }
Point operator* (db a, Point b){ return {a * b.x, a * b.y}; }
Point operator/ (Point a, db b){ return {a.x / b, a.y / b}; }
db operator* (Point a, Point b){ return a.x * b.x + a.y * b.y; }
db operator^ (Point a, Point b){ return a.x * b.y - a.y * b.x; }
bool operator!= (Point a, Point b) { return a.x != b.x || a.y != b.y; }
bool operator== (Point a, Point b) { return a.x == b.x && a.y == b.y; }
bool operator< (Point a, Point b){
	if (a.x == b.x) return a.y < b.y;
	return a.x < b.x;
}

int quad(Point a){ return sign(a.y) == 1 || (sign(a.y) == 0 && sign(a.x) >= 0);}
db dot(Point a, Point b){ return a.x * b.x + a.y * b.y; };
db cross(Point a, Point b){ return a.x * b.y - a.y * b.x; }
db cross(Point p1, Point p2, Point p0){ return cross(p1 - p0, p2 - p0); }
db area(Point a, Point b, Point c){ return cross(b - a, c - a); }
bool onLine(Point a, Point b, Point c){ return sign(cross(b, a, c)) == 0; }
bool onLine(Point p, Line l){ return onLine(p, l.a, l.b); }

//向量旋转90度后的新向量
Point rotate(Point p1, Point p2){
    Point vec = p1 - p2;
    return {-vec.y, vec.x};
}
db get_dist2(Point a, Point b){
    db dx = a.x - b.x;
    db dy = a.y - b.y;
    return dx * dx + dy * dy;
}
Line midSegment(Line l){
    Point mid = (l.a + l.b) / 2;
    return {mid, mid + rotate(l.a, l.b)};
}

Point p[N * 2];
Line l[N * N];

int main()
{
    int n; cin >> n;
    for (int i = 0; i < n; i++){
        db x, y; cin >> x >> y;
        p[i] = {x, y};
    }
    auto check = [&](Point p, Line l){
        if (l.a.x == l.b.x){
            return (l.a.x == p.x || l.b.x == p.x);
        }
        return onLine(p, l);
    };
    int ans = 0;
    map<Point, vector<Line>> mp;
    for (int i = 0; i < n; i++){
        for (int j = i + 1; j < n; j++){
            Point vec1 = p[i] - p[j], vec2 = p[j] - p[i];
            mp[vec1].push_back({p[i], p[j]}), mp[vec2].push_back({p[j], p[i]});
        }
    }
    for (auto [T, vec] : mp){
        int sz = vec.size();
        // A:vec[i].a B: vec[i].b C:vec[j].a D:vec[j].b
        for (int i = 0; i < sz; i++){
            for (int j = 0; j < sz; j++){
                if (i == j) continue;
                Point A = vec[i].a, B = vec[i].b, D = vec[j].a, C = vec[j].b;
                if (dot(B - A, D - A) == 0){
                    Line mid = midSegment({A, B});
                    for (int k = 0; k < n; k++){
                        if (check(p[k], mid) && dot(A - D, p[k] - D) < 0){
                            ans++;
                        }
                    }
                }
            }
        }
    }

    cout << ans / 2;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值