2021牛客暑期多校 1H - Hash Function (多項式乘法FFT、類埃氏篩)
題意
給定\(n\)個互不相同的範圍在\([0,500000]\)內的數
要求求出最小的模數\(seed\),使得所有數與\(seed\)取模後仍是互不相同的
思路(快速傅立葉變換)
大部分隊伍都是直接當想法題過掉的,本篇給出使用多項式乘法的解法
首先,答案的最小值應是數字的數量\(n\),最大值應是數字的最大值\(+1\)
所以得出\(seed\in[n,500001]\)(根據輸入可以再縮小,但沒必要)
然後考慮本題要求
假如我們當前選擇了一個\(seed\),使得某兩個數\(a,b\)對其取模後相同,即
\[a\%seed = b\%seed \]換言之,實際上\(a\)
所以對於\(seed\)的選取,一定不能是任意兩個數的差值(或者這個差值的因子)
所以需要處理出這些數字兩兩之間的差值,這裡可以藉助多項式乘法將\(O(n^2)\)的列舉優化成\(O(nlogn)\)
由於計算的是\(a-b\),並且(根據\(FFT\)板子易知)在多項式乘法中不允許出現負數下標(因為多項式乘法原本計算的是兩兩之和的種類數\(a+b\),而不是本題中兩兩之差)
為了能讓\(a\)和\(-b\)能夠分別儲存,所以我們需要為每個數加上一個基礎值\(avg\),使得\(a+avg\)和\(-b+avg\)
顯然,\(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; }