1. 程式人生 > 其它 >2021牛客暑期多校 1H - Hash Function (多項式乘法FFT、類埃氏篩)

2021牛客暑期多校 1H - Hash Function (多項式乘法FFT、類埃氏篩)

2021牛客暑期多校 1H - Hash Function

題意

給定\(n\)個互不相同的範圍在\([0,500000]\)內的數

要求求出最小的模數\(seed\),使得所有數與\(seed\)取模後仍是互不相同的

思路(快速傅立葉變換)

大部分隊伍都是直接當想法題過掉的,本篇給出使用多項式乘法的解法

首先,答案的最小值應是數字的數量\(n\),最大值應是數字的最大值\(+1\)

所以得出\(seed\in[n,500001]\)(根據輸入可以再縮小,但沒必要)

然後考慮本題要求

假如我們當前選擇了一個\(seed\),使得某兩個數\(a,b\)對其取模後相同,即

\[a\%seed = b\%seed \]

換言之,實際上\(a\)

\(b\)的差值也就是\(seed\)的倍數,即

\[|a-b|\%seed=0 \]

所以對於\(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;
}