题意:有5个集合,集合的大小是n,每一个集合出一个数,问能不能找到五个数的和为0。数据范围:T<=50;n<=200
分析:
暴力枚举是n^5*T,超时,那么就要用一些技巧了。
这里有一个指针的妙用:如何在O(n)的复杂度找A,B,使得A+B==C(A,B分别属于一个数列a,b)。做法是先把a,b分别按升序排序,然后一个指针i指向a的首,指针j指向b的尾,判定指针指向的数的和是否==C,若等于则结束查找,若小于,则i++,若大于则 j- -,如果有一个指针已经走到了头还没找到A+B==C,则说明不存在这样的AB;
这个问题再变形:有三个数列a,b,c,如何在O(n^2)的复杂度找A,B,C,使得A+B==C。方法还是上面那个,就是多了一个遍历C的循环。
本题就是用了这个技巧,还用了一点分治的思想吧,其实不是分治,反正我就这样记了,就是说不要题目给的一个问题就想着把这个问题整体的解决,题目说求和,我们不是一定非要一步到位的求和啊,我们可以把求和这个问题分成:a1+a2;a3+a4;a5 求和就是(a1+a2)+(a3+a4)+a5,又因为是找等于0,所以就是:(a1+a2)+(a3+a4)==-a5,这样不就把问题转换成上面的模型了吗。
另外,排序+去重可以用set来做,set.insert()的时候会自动去重并且按升序排序。
代码:
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
set<long long> s1,s2;
int t,n;
long long a[6][300];
long long sum[3][50000];
int main()
{
cin>>t;
while(t--){
cin>>n;
s1.clear();
s2.clear();
for(int i=0;i<5;i++){
for(int j=0;j<n;j++){
cin>>a[i][j];
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
s1.insert(a[0][i]+a[1][j]);
s2.insert(a[2][i]+a[3][j]);
}
}
for(int i=0;i<n;i++) a[4][i]*=(-1);
int len1=0,len2=0;
set<long long>::iterator it;
for(it=s1.begin();it!=s1.end();it++) sum[1][len1++]=(*it);
for(it=s2.begin();it!=s2.end();it++) sum[2][len2++]=(*it);
int ok1=0;
for(int i=0;i<n;i++){
int ok=1;
int j=0,k=len2-1;
while(sum[1][j]+sum[2][k]!=a[4][i]){
if(sum[1][j]+sum[2][k]<a[4][i]) j++;
else k--;
if(j>=len1||k<0){
ok=0;break;
}
}
if(ok){
ok1=1;break;
}
}
if(ok1) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}