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

https://loj.ac/p/6065

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

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

对于 (2) 条边只由一根线段组成的情况,还是枚举这个 (L),但此时若再枚举另一根线段则很难再计算
所以考虑在排序后的数列上尺取,对于每个 (l) 每次找到 (r)(如果有)使得 (a_l+a_r=L),此时如果剩余两条边的情况都是一根 (a_l) 和一根 (a_r) 组成,那么对答案的贡献就是 (inom{num_{a_l}}{2}inom{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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/suxxsfe/p/15413695.html