1. 程式人生 > 其它 >LOJ6065「2017 山東一輪集訓 Day3」第一題

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;
}