WEEK4 周记 作业B题——二分算法_四个数列
一、题意
1.简述
有四个数列 A,B,C,D,每个数列都有 n 个数字。从每个数列中各取出一个数,问有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
2.输入格式
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)。
3.输出格式
输出不同组合的个数。
4.样例
Input
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
Output
5
Hint
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
二、算法
粗糙的超时的想法
四层循环暴力求解,时间复杂度为O(n4)O(n^4)O(n4),由题目给出的数据范围知,必然会超时。
思路的改进
由题目给出的数据范围知,算法的时间复杂度应在O(n2)O(n^2)O(n2)左右,当然也可以更小。
a+b+c+d=0a+b+c+d=0a+b+c+d=0a+b=−(c+d)a+b=-(c+d)a+b=−(c+d)
所以我们可以将A和B数组分为一组求和,将C和D数组分为一组求和,分别在两个和序列中寻找互为相反数的数对,这样的数对对应着一组满足题目要求的四元组。
怎么找这样的数对呢?
考虑二分的思路。我们可以遍历一个和序列,同时使用二分的方法在另一个和序列(提前排好序)中寻找当前元素的相反数。
这样,求和序列的时间复杂度为O(n2)O(n^2)O(n2),对和序列排序的时间复杂度为O(2n2log(n))O(2n^2log(n))O(2n2log(n)),二分找数对的时间复杂度为O(2n2log(n))O(2n^2log(n))O(2n2log(n)),所以总的时间复杂度为O(n2log(n))O(n^2log(n))O(n2log(n))。
三、总结与收获
STL的超时问题
这个题用map做代码好写,时间复杂度也是O(2n2log(n))O(2n^2log(n))O(2n2log(n)),但是仍会超时。这个时候我们就得考虑优化。
事实上,我们只能优化的部分是复杂度前面的系数,用map增大的也是这个系数。
最终不用map而是手写才AC。
另外还需要注意的是,能不用long long int就不用,会增大时间。
举个例子来说, long long int a,b和int a,b,a+b的时间前者是后者好几倍。
总的来说,就是在均衡手写效率和时间效率的基础上,能不用STL就不用。
unordered map的常数要比map小。
下面给出超时的代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>
#define llll long long int
//map会超时
//long long a+b要比int a+b慢,所以除非不得已不要轻易用longlong
using namespace std;
int a[4][4001];
map<int,int> ab;
map<int,int> cd;
int sum;
int main()
{
int n;
scanf("%d",&n);
int i,j;
for(i=0;i<n;i++)
for(j=0;j<4;j++)
scanf("%d",&a[j][i]);
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
ab[a[0][i]+a[1][j]]++;
cd[a[2][i]+a[3][j]]++;
}
map<int,int>::iterator it=ab.begin();
for(;it!=ab.end();it++)
{
pair<const int,int> pr=*it;
map<int,int>::iterator temp=cd.find(0-pr.first);
if(temp!=cd.end())
sum+=(*it).second * (*temp).second;
}
printf("%d",sum);
return 0;
}
四、代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>
#define llll long long int
using namespace std;
int a[4][4001];
int ab[8000001];
int abnew[8000001];
int abnum[8000001];
int cd[8000001];
int sum;
int indexab;
int indexcd;
int main()
{
int n;
scanf("%d",&n);
int i,j;
for(i=0;i<n;i++)
for(j=0;j<4;j++)
scanf("%lld",&a[j][i]);
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
ab[indexab]=a[0][i]+a[1][j];
indexab++;
cd[indexcd]=a[2][i]+a[3][j];
indexcd++;
}
sort(ab,ab+indexab);
int index=0;//abnew
for(i=0;i<indexab;i++)
{
int* l=lower_bound(ab,ab+indexab,ab[i]);//低于C++11就不支持了
int* r=upper_bound(ab,ab+indexab,ab[i]);
i=r-ab-1;
abnew[index]=ab[i];
abnum[index]=r-l;
index++;
}
int it;
for(it=0;it<indexcd;it++)
{
int* l=lower_bound(abnew,abnew+index,0-cd[it]);
if(abnew[l-abnew]==0-cd[it])
sum+=abnum[l-abnew];
}
printf("%d",sum);
return 0;
}
本文探讨了一道经典的算法问题,即从四个数列中各取一个数,使其和为0,提出了一种时间复杂度为O(n²logn)的优化算法。通过将数列两两分组并求和,利用二分查找找到互为相反数的数对,避免了原始的四层暴力循环。此外,文章还讨论了使用STL如map的超时问题及优化策略。

被折叠的 条评论
为什么被折叠?



