2021牛客暑期多校 1H - Hash Function (多项式乘法FFT、类埃氏筛)

时间:2021-07-17
本文章向大家介绍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;
}

原文地址:https://www.cnblogs.com/stelayuri/p/15024453.html