题解:P8398 [CCC 2022 S4] Good Triplets

【题目传送门】

题意分析:

给定一个圆和圆周上均匀分布的点,求能包括圆心的三角形的数量。
每个点都有编号,点可以重叠,不同编号的点形成的相同形状的三角形视为不同的三角形。

很明显的一个事情是,如果我们构成的三角形的某两个点在同一个位置,这明显不是三角形,而退化成了点或者线。

这题和计算几何没有任何关系。我们直接包含原点的三角形不好统计,转换一下思路,我们容斥原理,先统计现在所有点能形成三角形的个数。这个统计完了呢?再统计原点在三角形周长上或在三角形外面的点。

而且我断环成链了一下,感觉这样好处理。事实上也是这样的。

如何计算?

\(Step\ 1\)

设每个位置 \(i\) 上的点的个数为 \(k_i\),先求出所有三元组的方案个数 \(X_1\)

\[n=\sum k_i\\ X_1 = \binom{n}{3} \]

然后我们计算出非法情况 \(X_2\)(两个点重叠)和 \(X_3\)(三个点重叠):

\[X_2 = \sum_{k_i\ge2}[\binom{k_i}{2} \times (n-k_i)]\\ X_3 = \sum_{k_i\ge3}\binom{k_i}{3} \]

那么我们的总方案数 \(X_t\) 就呼之欲出了:

\[\begin{equation} \begin{split} X_t &=X_1-X_2-X_3\\ &=\binom{n}{3} - \sum_{k_i\ge2}[\binom{k_i}{2} \times (n-k_i)] - \sum_{k_i\ge3}\binom{k_i}{3}\\ &=total\_combination - line\_situation - point\_situation\\ \end{split} \end{equation} \]

\(Step\ 2\)

现在我们得到了总方案数,现在的目的就是筛选(计算)出所有不包含原点的三角形了。
我们不难得到一个结论:如果三角形的三个顶点全都在圆的某个直径的同一侧,那么这个三角形一定不包含原点。这边不给出证明,感性理解即可。

这要怎么算?我们枚举直径?很明显,我旁边的大牛调了一个小时没写出来的事实,告诉我最好不要这么处理。我们发现可以枚举一个顶点,然后对它所在的一条直径的一侧的点集进行方案统计更好处理。

这个的正确性如何保证?我们令我们统计方案的顺序为逆时针,我们枚举到当前点 \(i\) 的时候计算一遍上述过程,然后再遍历到下一个点 \(i'=i+1\),计算 \(i'\) 一侧的点击的时候,因为遍历顺序是逆时针的,且计算点集也是逆时针的,所以统计 \(i'\) 所下辖的方案的时候必定不会出现 \(i\),而统计 \(i\) 所下辖的方案的时候必定会出现 \(i\)。所以我们能保证这个遍历顺序不会出现重复统计的情况。

我们先蓷柿子,遍历到点 \(i\) 的时候的方案总数:

\[j\in[i+1,i+\frac{\ c\ }{\ 2\ }]\\ X'=k_i\times[\binom{\sum k_j}{2} - \sum_{k_j\ge2}\binom{k_j}{2}] \]

你的虱子飞来飞去的,那你的时间复杂度是不是也是飞来飞去的呢?
很遗憾,我们前缀预处理三个东西:每个位置的点个数前缀和,每个位置的组合数,每个位置的组合数的前缀和。上述的预处理时间复杂度为 \(\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();
}
posted @ 2025-11-27 15:15  Noivelist  阅读(21)  评论(0)    收藏  举报