suxxsfe

一言(ヒトコト)

LOJ6065「2017 山东一轮集训 Day3」第一题

https://loj.ac/p/6065

考虑 \(6\) 个线段组成正方形,必有 \(2\)\(3\) 条边是只由一根线段组成
由于独立所以可以分别处理这两种情况

对于 \(3\) 条只由一根线段组成比较简单,为了防止那一条由三根线段组成的边计数重复,可以排序以后从小到大枚举 \(i\) 表示这三条线段中最大的那个,再枚举一个 \(L\) 表示那三个只由一根线段组成的边的长度
维护一个 \(num_x\) 表示有多少个 \(a_i=x\),再维护 \(cnt_x\) 表示有多少对 \(p,q\) 满足 \(p,q\in [1,i),a_p+a_q=x\)
所以此时对答案的贡献就是 \(\binom{num_L}{3}cnt_{L-a_i}\),对 \(cnt\) 的更新也是简单的

对于 \(2\) 条边只由一根线段组成的情况,还是枚举这个 \(L\),但此时若再枚举另一根线段则很难再计算
所以考虑在排序后的数列上尺取,对于每个 \(l\) 每次找到 \(r\)(如果有)使得 \(a_l+a_r=L\),此时如果剩余两条边的情况都是一根 \(a_l\) 和一根 \(a_r\) 组成,那么对答案的贡献就是 \(\binom{num_{a_l}}{2}\binom{num{a_r}}{2}\)
另一种可能是一条边由一根 \(a_l\) 一根 \(a_r\) 组成,另一条边由之前枚举的某个 \(a_{l'},a_{r'}\) 组成
所以维护一个 \(sum\) 表示已经枚举过的 \(num_{a_l},num_{a_r}\) 的乘积,则此时对答案的贡献是 \(num_{a_l}num_{a_r}sum\)
要特殊讨论 \(l=r\) 的情况

#define N 5006
#define V 10000006
int n;
int a[N];
int cnt[V],num[V];
inline void add(int x){if(x<=1e7) cnt[x]++;}
inline long long _2(long long num){return num*(num-1)/2;}
inline long long _3(long long num){return num*(num-1)*(num-2)/6;}
inline long long _4(long long num){return num*(num-1)*(num-2)*(num-3)/24;}
int main(){
//		freopen("yist7.in","r",stdin);
	n=read();
	for(int i=1;i<=n;i++) a[i]=read(),num[a[i]]++;
	std::sort(a+1,a+1+n);
	add(a[1]+a[2]);
	long long ans=0;
	for(int i=3;i<=n;i++){//3 相等
		for(int j=i+1;j<=n;j++)if(a[j]^a[j-1]) ans+=_3(num[a[j]])*cnt[a[j]-a[i]];
		for(int j=1;j<i;j++) add(a[i]+a[j]);
	}
	n=std::unique(a+1,a+1+n)-a-1;
	for(int i=1;i<=n;i++)if(num[a[i]]>=2){
		long long now=0,sum=0;
		for(int l=1,r=i-1;l<=r;l++){
			while(l<=r&&a[l]+a[r]>a[i]) r--;
			if(a[l]+a[r]!=a[i]||l>r) continue;
			if(l==r) now+=_4(num[a[l]])+sum*_2(num[a[l]]),sum+=_2(num[a[l]]);
			else now+=_2(num[a[l]])*_2(num[a[r]])+sum*num[a[l]]*num[a[r]],sum+=num[a[l]]*num[a[r]];
		}
		ans+=now*_2(num[a[i]]);
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-10-16 11:29  suxxsfe  阅读(47)  评论(0编辑  收藏  举报