题目描述:?小花有一个数组A,小树有一个数组B。小花和小树的关系很好,他们希望合并手中的数组,得到新的集合C={a+b|a∈A, b∈B}。
输入格式:第一行输入两个整数N,M,分别表示数组A,B的长度。第二行包含N个整数,表示数组A。第三行包含M个整数,表示数组B。
? (0 ≤ N,M ≤ 2 *10^5, 0 ≤ A[i],B[i] ≤ 2 * 10^6)
输出格式:输出一个整数,表示C的元素个数。
输入样例:
10 10
12 14 0 2 2 15 26 17 8 44
1 4 6 10 22 13 19 50 39 0
输出样例:
56
注意:本题时间限制较严格,由于暴力解法时间复杂度为O(n^2),可能无法满足本题要求,因此请尽可能优化你的算法( 6000ms,512MB)。
分析:我们首先想到的算法就是暴力解法,用双重循环实现。源程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int n, m;
scanf("%d %d", &n, &m);
int* arrl=(int*)malloc(sizeof(int) * n);
int* arr2=(int*)malloc(sizeof(int) * n);
if (arrl==NULL || arr2 == NULL) return 1;
for (int i = 0; i < n; ++i) {
scanf("%d",&arrl[i]);
}
for(int i = 0; i< m; ++i) {
scanf("%d",&arr2[i]);
}
char* check =(char*)malloc(sizeof(char) * 4000001);
if (check == NULL) return 1;
memset (check, 0, sizeof(char) * 4000001);
int count= 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
check[arrl[i] + arr2[j]]=1;
}
}
for(int i=0; i< 4000001; i++){
count += check[i];
}
printf("%d\n",count);
return 0;
}
暴力算法虽然简单,但数据量大时,运行时间会超时,因为算法的时间复杂度为O(n^2),无法满足题目要求。因此,必须找到一种解决方案。因此有网友提出一种方案,使用傅里叶变换实现。
设A数组[0, 1, 3, 5, 6]中的元素可看成多项式(1+x+x^3+x^5+x^6)的系数,B数组中[2,3,4]中的元素可看成多项式(x^2+x^3+x^4)的系数。计算这两个多项式的乘法(1+x+x^3+x^5+x^6)*(x^2+x^3+x^4),然后看结果中哪些项前面有系数,统计有系数项的个数,即为所求答案。
使用快速傅里叶变换进行多项式乘法运算,其时间复杂度为O(nlogn),应能满足题目要求。源程序如下。
#include <stdio.h>
#define LLONG long long
#define MAXN (1<<23)
#define mod 998244353 //质数,在编程中常被用来做模数
int id[MAXN];
int a[ MAXN],b[ MAXN];
int quickpow(int x, int b)
{
LLONG ans=1,t=x;
while(b){
if(b&1) ans=ans*t%mod;
t=t*t%mod;
b>>=1;
}
return ans;
}
int init(int len)
{
int lim=1;
while(lim<=len) {
lim<<=1;
}
for(int i=1; i<lim; i++){
id[i] = (id[i>>1]>>1)+(i&1)*(lim>>1);
}
return lim;
}
void NTT(int a[],int lim, int opt)
{
for(int i=1; i<lim; i++){
if(i<id[i]) {
int step = a[i];
a[i] = a[id[i]];
a[id[i]] = step;
}
}
for(int len=2; len<=lim; len<<=1) {
int k=len>>1;
int wn=quickpow(3,(mod-1)/len);
for(int i=0; i<lim; i+=len){
LLONG g=1;
for(int j=0; j<k; j++,g=g*wn%mod){
int temp=a[i+j+k]*g%mod;
a[i+j+k]=(mod-temp+a[i+j])%mod;
a[i+j]=(a[i+j]+temp)%mod;
}
}
}
if(opt==-1) {
for (int i = 0; i < lim; i++){
int temp = a[i+1];
a[i+1] = a[lim - i];
a[lim - i] = temp;
}
LLONG inv=quickpow(lim,mod-2);
for(int i=0; i<lim; i++) {
a[i]=a[i]*inv%mod;
}
}
return;
}
int main()
{
int n,m,lim=0;
int x,i;
scanf("%d%d",&n,&m);
for(i=1; i<=n; i++) {
scanf("%d",&x);
a[x]=1;
if(x>lim)lim=x;
}
for(i=1; i<=m; i++) {
scanf("%d",&x);
b[x]=1;
if(x>lim)lim=x;
}
lim = init(lim<<1);
NTT(a,lim,1);
NTT(b,lim,1);
LLONG step;
for(i=0; i<lim; i++){
step = a[i];
a[i]=step*b[i]%mod;
}
NTT(a,lim,-1);
for(i=0,n=0; i<lim; i++) {
if(a[i]) n++;
}
printf("%d",n);
return 0;
}
参考文献:
[1]https://tieba.baidu.com/p/8844914020
[2]百度网盘资源下载网址
https://pan.baidu.com/s/17ZXphwqySNIsIgcGtYMjvg?pwd=lhwc
[3]李红卫,李秉璋.?C程序设计与训练(第四版)[M],大连,大连理工大学出版社,2023.