题解 [ABC418E] Trapezium

· · 题解

卡常题目。

题意

二维平面上有 N 个不同的点,保证任意三点不共线。

在所有组合中,以这四点为顶点能组成梯形的组合有多少种?

这里的梯形定义有所不同,平行四边形和矩形都可以认为是梯形。

分析

在任意三点不共线的情况下,相同斜率的任意两条边都可以组成梯形。

为了避免浮点误差以及平行于 y 轴的特殊情况,斜率可以采用分数表示。

但是平行四边形(包括矩形)有两组对边平行,同一个平行四边形会被计算两次。根据平行四边形的判定定理,统计所有的线段的中点,中点位置相同的都可以组成平行四边形。

使用 std::map 维护二元组即可。

时间复杂度 O(N^2 \log N),当然也可以通过重载哈希函数使用 std::unordered_map 做到 O(N^2)

代码

//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
int n;
int x[2002],y[2002];
struct FRAC{//分数结构体。
    int a,b;//分子和分母。
    FRAC(const int _a=0,const int _b=1):a(_a),b(_b){
        int g=__gcd(a,b);
        a/=g,b/=g,b<0&&(a=-a,b=-b);//保证分母非负,避免比较出现问题。
    }
    bool operator < (const FRAC&B)const{return (LL)a*B.b<(LL)B.a*b;}//根据分数定义重载小于运算符。
    bool operator == (const FRAC&B)const{return a*B.b==B.a*b;}//同上。
};
map<FRAC,int> M;//维护斜率。
map<PII,int> S;//维护中点。
int main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x[i]>>y[i];
        for(int j=1;j<i;j++)
            ++M[FRAC(y[i]-y[j],x[i]-x[j])],//计算斜率。
            ++S[PII(x[i]+x[j],y[i]+y[j])];//中点坐标整体乘 2 避免浮点数运算。
    }
    LL ans=0;
    for(const auto&[a,b]:M) ans+=b*(b-1ll)/2;//对于每一个斜率计算 C(b,2)。
    for(const auto&[a,b]:S) ans-=b*(b-1ll)/2;//对于平行四边形类似减去重复答案。
    cout<<ans<<'\n';
    return 0;
}