题意:
从N(N<=1000)个数里找四元组(i, j, p, q)满足i, j, p, q两两不同,并且i < j, p < q, Ai + Aj = Ap + Aq的数量。
思路:
计数 + 容斥原理:
从N的范围,这题的解法必须是O(n^2) 附近的解法。我们可以思考遍历i,j时候,能不能O(1)知道A[i]+A[j]=x的对数呢?
是可以的,预处理出num[A[i]],sum[A[i]+A[j]] 表示:A[i] 出现次数,A[i]+A[j] 和出现的次数。
考虑遍历到 i,j,A[i] + A[j] = x 时:
由容斥原理:能找到在坐标i,j 之后 和为 x 的组合对数 = num[A[i]+A[j]] (和为x总次数,包括A[i],A[j]的组合) - 出现过A[i]或者A[j]的和为x的组合数。
而出现过A[i]或者A[j]的和为x的组合数,有两种思路:
第一种直接:
当A[i]!=A[j] , 如果现在i,j时红线,下标1,3。还可能出现包含A[i]的 A[i] +A[k] =x对数数量一定是 num[A[j]]。 如图,下标1的1除了红线一条还有两条浅蓝色。同理,包含A[j]的A[k]+A[j]=x的对数数量是num[A[i]],这样计算红色线即包含A[i]又包含A[j]的被算了两次,所以简1。
ans =sum[A[i]+A[j]] -( num[A[i]] + num[A[j]]-1)
当**A[i] = A[j]**时候,包含A[i]+A[k]和A[k]+A[j]的数量都需要减一。
ans =sum[A[i]+A[j]] -(( num[A[i]]-1) + (num[A[j]]-1)-1)
第二种思路,这里再用容斥原理思想:
当A[i] != A[j] : 已知出现A[i],A[j]次数为num[i],num[j],则总共和为X的不同组合数为num[A[i]]*num[A[j]]
,去掉A[i],A[j],还有(num[A[i]]-1)*(num[A[j]] - 1)
,则包括A[i],A[j]组合数为 num[A[i]]*num[A[j]] - (num[A[i]]-1)*(num[A[j]]-1)
。
当A[i] == A[j]: 已知出现A[i],A[j]次数为num[i],num[j],则总共和为X的不同组合数为num[A[i]]*(num[A[j]]-1)/2
,去掉A[i],A[j],还有(num[A[i]]-2)*(num[A[j]] - 3)/2。
当然,这两种思路化简后公式是一样的,详见代码。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
map<ll, int> num,sum;
int A[N];
int main(){
int n;
while(~scanf("%d",&n)){
num.clear(); sum.clear();
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
num[A[i]]+=1;
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
sum[A[i]+A[j]]+=1;
}
}
ll cnt = 0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
//if(A[i] == A[j]) cnt += sum[A[i]+A[j]] - (num[A[i]] * (num[A[j]]-1)/2 - (num[A[i]]-2) *(num[A[j]]-3)/2);
//else cnt += sum[A[i]+A[j]] - (num[A[i]] * num[A[j]] - (num[A[i]]-1) *(num[A[j]]-1));
if(A[i] == A[j]) cnt += sum[A[i]+A[j]] - (num[A[i]] + num[A[j]]-3);
else cnt += sum[A[i]+A[j]] - (num[A[i]] + num[A[j]]-1);
}
}
printf("%lld\n", cnt);
}
}