题面
http://acm.hdu.edu.cn/showproblem.php?pid=5784
题解
由于锐角三角形难以计算,我们考虑用(C_n^3)减去钝角、直角三角形和三点共线。这样做的好处是,钝角和直角由于每个三角形中最多一个,只需要枚举一个角即可;而三点共线也只需枚举中间的那个点。
对于每一个点,将其他所有点到该点的连线进行极角排序,然后从排好序的连线中,顺序枚举角的始边,那么直角或钝角的终边就处于始边旋转([{frac{pi}{2},pi}))的范围中,三点共线的终边就是始边的反向。由于“始边旋转([frac{pi}{2},pi))的范围”的左、右端点单调不减,可以时刻维护它们,从而得到对应该始边的直、锐角个数。三点共线个数的统计类似,但是最后应除以2,因为假设A,B,C共线,那么AB为始边会将C计入一次,BC为始边又会将A计入一次。
时间复杂度(O(n^2 log n))。瓶颈为排序。
代码
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define rg register
#define In inline
#define ld long double
const ll N = 2000;
const ld eps = 1e-18;
const ld pi = acos(-1.0);
In int sgn(ld x){
return x < -eps ? -1 : x > eps;
}
struct vec{
ll x,y;
vec(){}
vec(ll _x,ll _y){x = _x,y = _y;}
In friend vec operator + (vec a,vec b){
return vec(a.x + b.x,a.y + b.y);
}
In friend vec operator - (vec a,vec b){
return vec(a.x - b.x,a.y - b.y);
}
}p[N+5];
ll n,ln;
ld link[2*N+5];
ll Calc(int k){
ll L = 1,R = 0,P = 0,s1 = 0,s2 = 0;
for(rg int i = 1;i <= k;i++){
ld n1 = link[i] + pi / 2,n2 = link[i] + pi;
while(sgn(link[L]-n1) < 0)L++; //L是当前始边的第一个直角或钝角的下标
while(sgn(link[R+1]-n2) < 0)R++; //R是最后一个直角或钝角的下标
P = R;
while(sgn(link[P+1]-n2) <= 0)P++; //P是最后一个平角的下标
s1 += R - L + 1;
s2 += P - R; //平角
}
return s1 + (s2 >> 1); //防平角重复
}
int main(){
while(~scanf("%lld",&n)){
for(rg int i = 1;i <= n;i++){
ll x,y;
scanf("%lld%lld",&x,&y);
p[i] = vec(x,y);
}
ll ans = n * (n - 1) * (n - 2) / 6;
for(rg int i = 1;i <= n;i++){
ln = 0;
for(rg int j = 1;j <= n;j++)
if(i != j)link[++ln] = atan2l(p[j].y - p[i].y,p[j].x - p[i].x);
sort(link + 1,link + ln + 1);
int k = ln;
for(rg int j = 1;j <= k;j++)
link[++ln] = link[j] + 2 * pi;
link[++ln] = 3.5 * pi; //防止溢出
ans -= Calc(k);
}
printf("%lld
",ans);
}
return 0;
}