題意
給定\(n\)個互不相同的范圍在\([0,500000]\)內的數
要求求出最小的模數\(seed\),使得所有數與\(seed\)取模后仍是互不相同的
思路(快速傅里葉變換)
大部分隊伍都是直接當想法題過掉的,本篇給出使用多項式乘法的解法
首先,答案的最小值應是數字的數量\(n\),最大值應是數字的最大值\(+1\)
所以得出\(seed\in[n,500001]\)(根據輸入可以再縮小,但沒必要)
然后考慮本題要求
假如我們當前選擇了一個\(seed\),使得某兩個數\(a,b\)對其取模后相同,即
換言之,實際上\(a\)與\(b\)的差值也就是\(seed\)的倍數,即
所以對於\(seed\)的選取,一定不能是任意兩個數的差值(或者這個差值的因子)
所以需要處理出這些數字兩兩之間的差值,這里可以借助多項式乘法將\(O(n^2)\)的枚舉優化成\(O(nlogn)\)
由於計算的是\(a-b\),並且(根據\(FFT\)板子易知)在多項式乘法中不允許出現負數下標(因為多項式乘法原本計算的是兩兩之和的種類數\(a+b\),而不是本題中兩兩之差)
為了能讓\(a\)和\(-b\)能夠分別存儲,所以我們需要為每個數加上一個基礎值\(avg\),使得\(a+avg\)和\(-b+avg\)都在\(0\)以上
顯然,\(avg\ge 500000\)
然后跑一遍\(FFT\),求出所有\((a+avg)+(-b+avg)\)的種類數
將得到的多項式提取出來,\((a+avg)+(-b+avg)-avg\times 2\)也就是\(a-b\)的值,將每種差值是否出現記錄在\(vis\)數組中
接下來就是最后一步,將所有出現的差值及其因子直接排除,選出最小的答案
枚舉因子可能需要\(O(n\sqrt{n})\),可能會炸
所以我們直接枚舉\(seed\in[n,500001]\),對於某個可能是答案的數(不是差值),找一下是否存在差值是其倍數(直接枚舉倍數即可),如果沒有差值是其倍數,則找到了答案
#include<bits/stdc++.h>
using namespace std;
const int N=3000050;
const double PI=acos(-1.0);
const int avg=500000;
int vis[500050];
int lim=1,rev[N];
struct cp
{
double x,y;
cp(double u=0,double v=0){x=u,y=v;}
friend cp operator +(const cp &u,const cp &v){return cp(u.x+v.x,u.y+v.y);}
friend cp operator -(const cp &u,const cp &v){return cp(u.x-v.x,u.y-v.y);}
friend cp operator *(const cp &u,const cp &v){return cp(u.x*v.x-u.y*v.y,u.x*v.y+u.y*v.x);}
}f[N],g[N];
void FFT(cp *a,int tp)
{
for(int i=0;i<lim;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int md=1;md<lim;md<<=1)
{
cp rt=cp(cos(PI/md),tp*sin(PI/md));
for(int stp=md<<1,pos=0;pos<lim;pos+=stp)
{
cp w=cp(1,0);
for(int i=0;i<md;i++,w=w*rt)
{
cp x=a[pos+i],y=w*a[pos+md+i];
a[pos+i]=x+y;
a[pos+md+i]=x-y;
}
}
}
}
void initFFT(int n)
{
int lg=0;
while(lim<=n)
lg++,lim<<=1;
for(int i=0;i<lim;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int d;
scanf("%d",&d);
//記錄d和-d
f[avg+d].x+=1;
g[avg-d].x+=1;
}
//根據乘法的值域預處理FFT蝴蝶變換,加快FFT速度
initFFT(avg*4);
//正常FFT流程
FFT(f,1),FFT(g,1);
for(int i=0;i<lim;i++)
f[i]=f[i]*g[i];
FFT(f,-1);
//記錄是否存在差值為i的情況(i+avg*2即多項式乘法內的結果下標)
for(int i=1;i<=500001;i++)
vis[i]=(int)round(f[i+avg*2].x/lim);
//枚舉seed,判斷可行性
for(int i=n;i<=500001;i++)
{
if(vis[i]>0)
continue;
for(int j=i+i;j<=500000;j+=i)
{
//如果存在差值j(i的倍數),則i不可行
if(vis[j])
{
vis[i]=1;
break;
}
}
//i可行,作為模數輸出即可
if(vis[i]==0)
{
cout<<i<<'\n';
return 0;
}
}
return 0;
}