思想
问题规模较大时,无法枚举所有元素的组合,但能够枚举一半元素的组合,此时,将问题拆成两半后分别枚举,再合并他们的方法往往非常有效。
例如:
4 Values Whose Sum is 0(POJ No.2785)
题:给出四个数列,要求每个数列中选出一个数,四个数的和为0,问总共有几种选法。当一个数组中有多个相同的数字时,把他们作为不同的数字看待
限制条件
1<=n<=4000
|(数字的值)<=28 |
样例
输入
n=6
A={-45,-41,-36,-36,26,-32}
B={22,-27,53,30,-38,-54}
C={42,6,-37,-75,-10,-6}
D={-16,30,77,-46,62,45}
输出
5
传统做法:枚举四个数,时间复杂度可以达到O(n4),所以肯定是不行的
折半枚举法:将他们对半分成AB和CD再考虑,就可以快速解决了,从两个数列中选择只有n2种组合,所以可以枚举。
将A+B+C+D=0
转化为A+B=-(C+D)
处理,首先枚举所有的-(C+D)
进行排序,然后枚举所有的(A+B)
利用二分查搜索查找-(C+D)
数组,算法复杂度只有O(n2logn)、
/*
6
-45 -41 -36 -36 26 -32
22 -27 53 30 -38 -54
42 6 -37 -75 -10 -6
-16 30 77 -46 62 45
*/
#include<bits/stdc++.h>
using namespace std;
int n,A[10000],B[10000],C[10000],D[10000],CD[1000000],AB[1000000];
long long ans;
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>A[i];
}
for(int i=0;i<n;i++){
cin>>B[i];
}
for(int i=0;i<n;i++){
cin>>C[i];
}
for(int i=0;i<n;i++){
cin>>D[i];
}
//枚举 -(C+D)
int k1=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
CD[k1++]=-(C[i]+D[j]);
}
}
sort(CD,CD+k1);
//枚举(A+B)
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int AB=A[i]+B[j];
ans+=upper_bound(CD,CD+k1,AB)-lower_bound(CD,CD+k1,AB);//利用二分搜索找相等
}
}
cout<<ans<<endl;
return 0;
}
用map实现
/*
6
-45 -41 -36 -36 26 -32
22 -27 53 30 -38 -54
42 6 -37 -75 -10 -6
-16 30 77 -46 62 45
*/
#include<bits/stdc++.h>
using namespace std;
int n,A[10000],B[10000],C[10000],D[10000],AB[1000000];
long long ans;
map<int,int> mp;
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>A[i];
}
for(int i=0;i<n;i++){
cin>>B[i];
}
for(int i=0;i<n;i++){
cin>>C[i];
}
for(int i=0;i<n;i++){
cin>>D[i];
}
//枚举 -(C+D)
int k1=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
mp[-(C[i]+D[j])]++;
}
}
//枚举(A+B)
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int AB=A[i]+B[j];
ans+=mp[AB];
}
}
cout<<ans<<endl;
return 0;
}