题解 | #数三角形#
思路
因为 个点互不相同,因此我们任取三个点有 种可能,而其中在一条线上的点将不能形成三角形,我们设 个点可以形成 条线,其中第 条线上有 个点,则:
于是,我们统计 个点可以构成的每条线与每条线上的点数,即可在 时间内算出三角形个数。显然,线最多为 条,故可在 时间内计算出结果。 那么,如何统计线上的点数呢?我们遍历每个点对,计算点对的直线方程,将两个点插入到对应直线的集合中即可。如果我们能在 时间内找到直线对应的集合,则点对数目即为时间复杂度,即 。
自然地,我们想到可以用哈希表存储每条直线上对应的点集。我们写出直线的两点式方程:
化简,
因此,只要确定了、 与 三个系数,就能唯一确定一条直线。但是,一条直线的三个系数并不唯一,它们同时乘以某个因子仍然满足方程。因此需要分情况确定唯一的 、、,由于点互不相同,因此 、 不同时为 。
- 若 、 为 ,此时直线方程为 ,因此置 。
- 若 、 为 ,此时直线方程为 ,因此置 。
- 若仅有 为 ,我们可以让 、 除以其最大公因数保证唯一性。
- 若仅有 为 ,我们可以让 、 除以其最大公因数保证唯一性。
- 若仅有 为 ,我们可以让 、 除以其最大公因数保证唯一性。
- 若均不为 ,我们可以让 、、 均除以其最大公因数保证唯一性。
除此之外,还需保证 、、 的正负顺序,我们可以在 存在时保证 为正,否则保证 为正。
最后,我们知道 、、,因此 、、,故 、 可用一个 15
位无符号整数表示,可用一个 30
位无符号整数表示(实际上不需要这么多位),因此可用一个 long long
存储 ((C+20000)<<30)|((B+200)<<15)|(A+200)
表示一条直线,该值即为直线的哈希值。
代码
具体实现使用 hashLine
根据两点求直线哈希值,使用unordered_map
存储点集,使用unordered_set
对插入点去重。
#include <bits/stdc++.h>
#include <functional>
#include <ios>
using namespace std;
using ll = long long;
ll hashLine(ll x0, ll y0, ll x1, ll y1)
{
ll A = x1-x0;
ll B = y1-y0;
ll C = x1*y0-x0*y1;
if(A<0) { A=-A; B=-B;C=-C;}
else if(A==0 && B<0) { B=-B; C=-C; }
ll factor;
if(A==0 && C==0) factor=B;
if(A==0) factor = abs(gcd(B,C));
else if(B==0 && C==0) factor=A;
else if(B==0) factor = abs(gcd(A, C));
else if(C==0) factor = abs(gcd(A,B));
else factor = abs(gcd(A, gcd(B,C)));
A/=factor;
B/=factor;
C/=factor;
A+=200;
B+=200;
C+=20000;
return (C<<30)|(B<<15)|A;
}
int main() {
unordered_map<ll, unordered_set<int>> freq;
int n;
ios_base::sync_with_stdio(false);
cin>>n;
vector<pair<int,int>> pts(n);
for(auto &p: pts) cin>>p.first>>p.second;
for(int i=0;i<pts.size();++i) for(int j=i+1;j<pts.size();++j){
ll line = hashLine(pts[i].first, pts[i].second, pts[j].first, pts[j].second);
freq[line].insert(((pts[i].first+100)<<15)|(pts[i].second+100));
freq[line].insert(((pts[j].first+100)<<15)|(pts[j].second+100));
}
ll cnt = n*(n-1)*(n-2)/6;
for(auto &p: freq) {
ll m = p.second.size();
cnt -= m*(m-1)*(m-2)/6;
}
cout<<cnt<<endl;
return 0;
}
- 时间复杂度:,
hashLine
可在常量时间内完成,因此二重循环花费 时间。 - 空间复杂度:,哈希表中所有集合中的总点数为 。