Codeforces

Luogu

分析

考虑任意五个点对答案的贡献。显然有

  • 如果五个点的凸包为五边形,则贡献为 $0$;
  • 如果五个点的凸包为四边形,则贡献为 $1$;
  • 如果五个点的凸包为三角形,则贡献为 $2$。

我们只需要求出五个点凸包为为 $x$ 边形的方案数 $s_x$,然后答案即为 $s_4+2s_3$。

不知道怎么想到可以把答案拆成 $5(s_5+s_4+s_3)-(5s_5+4s_4+3_s3)$ 的形式,而又有 $s_5+s_4+s_3={n\choose 5}$,因此我们只需要计算 $5s_5+4s_4+3s_3$ 即可。这个东西的实际含义就是任意五个点的凸包所含的边数之和。

考虑枚举一条边并枚举它在多少个五个点的凸包中。我们先枚举一个点,然后把剩下的点极角排序并扫描(此时相当于枚举了一条边),则剩下 $3$ 个点都应在这条边左侧,每次找到范围后组合数算一下即可。

需要注意的是本题卡精度所以极角排序不能用 atan2,可以用叉积判断。

代码

// ===================================
//   author: M_sea
//   website: https://m-sea-blog.com/
// ===================================
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

const int N=2500+10;

int n,top,x[N],y[N];

struct Vector { int x,y; } sta[N<<1];
ll cross(Vector a,Vector b) { return 1ll*a.x*b.y-1ll*a.y*b.x; }
bool cmp(Vector a,Vector b) {
    int fa=a.y<0||(a.y==0&&a.x>0);
    int fb=b.y<0||(b.y==0&&b.x>0);
    return fa!=fb?fa<fb:cross(a,b)>0;
}

ll C(int n,int m) {
    if (n<m) return 0;
    ll res=1;
    for (int i=0;i<m;++i) res*=n-i,res/=i+1;
    return res;
}

int main() {
    n=read();
    for (int i=1;i<=n;++i) x[i]=read(),y[i]=read();
    ll ans=C(n,5)*5;
    for (int i=1;i<=n;++i) {
        top=0;
        for (int j=1;j<=n;++j)
            if (i!=j) sta[++top]=(Vector){x[j]-x[i],y[j]-y[i]};
        sort(sta+1,sta+top+1,cmp);
        for (int j=1;j<=top;++j) sta[j+top]=sta[j];
        for (int j=1,p=1;j<=top;++j) {
            while (p<j+top&&cross(sta[j],sta[p])>=0) ++p;
            ans-=C(p-j-1,3);
        }
    }
    printf("%I64d\n",ans);
    return 0;
}
最后修改:2021 年 03 月 24 日 05 : 11 PM