题解:P8398 [CCC 2022 S4] Good Triplets
【题目传送门】
题意分析:
给定一个圆和圆周上均匀分布的点,求能包括圆心的三角形的数量。
每个点都有编号,点可以重叠,不同编号的点形成的相同形状的三角形视为不同的三角形。
很明显的一个事情是,如果我们构成的三角形的某两个点在同一个位置,这明显不是三角形,而退化成了点或者线。
这题和计算几何没有任何关系。我们直接包含原点的三角形不好统计,转换一下思路,我们容斥原理,先统计现在所有点能形成三角形的个数。这个统计完了呢?再统计原点在三角形周长上或在三角形外面的点。
而且我断环成链了一下,感觉这样好处理。事实上也是这样的。
如何计算?
\(Step\ 1\)
设每个位置 \(i\) 上的点的个数为 \(k_i\),先求出所有三元组的方案个数 \(X_1\):
然后我们计算出非法情况 \(X_2\)(两个点重叠)和 \(X_3\)(三个点重叠):
那么我们的总方案数 \(X_t\) 就呼之欲出了:
\(Step\ 2\)
现在我们得到了总方案数,现在的目的就是筛选(计算)出所有不包含原点的三角形了。
我们不难得到一个结论:如果三角形的三个顶点全都在圆的某个直径的同一侧,那么这个三角形一定不包含原点。这边不给出证明,感性理解即可。
这要怎么算?我们枚举直径?很明显,我旁边的大牛调了一个小时没写出来的事实,告诉我最好不要这么处理。我们发现可以枚举一个顶点,然后对它所在的一条直径的一侧的点集进行方案统计更好处理。
这个的正确性如何保证?我们令我们统计方案的顺序为逆时针,我们枚举到当前点 \(i\) 的时候计算一遍上述过程,然后再遍历到下一个点 \(i'=i+1\),计算 \(i'\) 一侧的点击的时候,因为遍历顺序是逆时针的,且计算点集也是逆时针的,所以统计 \(i'\) 所下辖的方案的时候必定不会出现 \(i\),而统计 \(i\) 所下辖的方案的时候必定会出现 \(i\)。所以我们能保证这个遍历顺序不会出现重复统计的情况。
我们先蓷柿子,遍历到点 \(i\) 的时候的方案总数:
你的虱子飞来飞去的,那你的时间复杂度是不是也是飞来飞去的呢?
很遗憾,我们前缀预处理三个东西:每个位置的点个数前缀和,每个位置的组合数,每个位置的组合数的前缀和。上述的预处理时间复杂度为 \(\mathcal{O(n)}\),计算单次方案公式的时间复杂度为 \(\mathcal{O(1)}\),我们遍历统计方案的个数为 \(\mathcal{O(n)\times O(1) = O(n)}\)。代码也不难调,直接完结撒花了。
\(\mathcal {CODE}\):
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
inline void read(int &x){
x=0;
char ch=getchar();
while(!isdigit(ch)){
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
}
const int N=1e6+10;
int k[N<<1],n,c;
int f2[N<<1],f3[N<<1],pre[N<<1],prec2[N<<1];
int tot;
void init(){
tot+=n*(n-1)*(n-2)/6;
for(int i=0;i<c;i++){
if(k[i]>=2){
f2[i]=k[i]*(k[i]-1)/2;
f2[i+c]=f2[i];
tot-=f2[i]*(n-k[i]);
}
if(k[i]>=3){
f3[i]=k[i]*(k[i]-1)*(k[i]-2)/6;
f3[i+c]=f3[i];
tot-=f3[i];
}
}
pre[0]=k[0],prec2[0]=f2[0];
for(int i=1;i<2*c;i++){
pre[i]=pre[i-1]+k[i];
prec2[i]=prec2[i-1]+f2[i];
}
}
int tmp;
inline int get_tot(int l,int r){
int m=pre[r]-pre[l-1];
return m*(m-1)/2;
}
inline int get_catter(int l,int r){
return prec2[r]-prec2[l-1];
}
void solve(){
int res=0;
for(int i=0;i<c;i++){
if(k[i]==0)continue;
int l=i+1,r=i+c/2;
int tmp=k[i]*(get_tot(l,r)-get_catter(l,r));
res+=tmp;
}
cout<<tot-res<<endl;
}
main(void){
// freopen("triplets.in","r",stdin);
// freopen("triplets.out","w",stdout);
read(n);
read(c);
for(int i=1,x;i<=n;i++){
read(x);
k[x]++;
k[x+c]++;
}
init();
solve();
}

浙公网安备 33010602011771号