2021牛客暑期多校训练营1 H Hash Function FFT快速傅里叶变换

题意

给你 n n n个数,让你找出一个最小的数字 m m m,使得上述 n n n个数 % m \%m %m后值各不相同。

分析

上述问题等价于:对所有的 i i i j j j ∣ a i − a j ∣ % m ≠ 0 |a_i-a_j|\%m \neq 0 aiaj%m=0,即可求出 ∣ a i − a j ∣ |a_i-a_j| aiaj的所有可能取值,再进行检测。

A = { a 1 , a 2 . . . a n } , B = { b 1 , b 2 . . . b n } A=\{a_1,a_2...a_n\},B=\{b_1,b_2...b_n\} A={a1,a2...an}B={b1,b2...bn}如何快速求出 A + B A+B A+B的集合 C C C ??
将表达方式进行转换,将相加变成相乘(指数相加)。
A ( x ) = x a 1 + x a 2 + . . . + x a n A(x)=x^{a_1}+x^{a_2}+...+x^{a_n} A(x)=xa1+xa2+...+xan B ( x ) = x b 1 + x b 2 + . . . + x b m B(x)=x^{b_1}+x^{b_2}+...+x^{b_m} B(x)=xb1+xb2+...+xbm,使用 F F T FFT FFT,在 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度内进行求解。

如果是减法,相当于加负数,可以将当前值均加上一个偏移量 d e l t a delta delta,在进行计算,代码中直接取 N N N作为偏移量。

如何检测?
枚举当前数的倍数即可,使用数组标记。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
const double PI = acos(-1);
const int N = 5e5 + 10, M = N * 2;
const int delta = 500001;
const int maxn = 1<<20;
#define endl '\n'
#define x first
#define y second
int n;
int rev[maxn], bit, tot;
struct Complex{
    double x, y;
    Complex operator+ (const Complex& t) const { return {x + t.x, y + t.y}; }
    Complex operator- (const Complex& t) const{ return {x - t.x, y - t.y}; }
    Complex operator* (const Complex& t) const{ return {x * t.x - y * t.y, x * t.y + y * t.x}; }
}a[maxn], b[maxn];
 
void fft(Complex a[], int inv){
    for(int i = 0; i < tot; i++)
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    for(int mid = 1; mid < tot; mid <<= 1){
        Complex w1 = Complex({cos(PI/mid), inv * sin(PI/mid)});
        for(int i = 0; i < tot; i += mid * 2){
            Complex wk = Complex({1, 0});
            for(int j = 0; j < mid; j ++, wk = wk * w1){
                Complex x = a[i + j], y = wk * a[i + j + mid];
                a[i + j] = x + y, a[i + j + mid] = x - y;
            }
        }
    }
}
 
// 这些数%m的结果互不相同
// 任何两个数相减%seed != 0
// |ai - aj| % seed != 0
// 快速求出所有的|ai - aj|
int vis[N];
bool check(int x){
    for(int i = x; i <= N; i += x){
        if(vis[i]) return 0;
    }
    return 1;
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n;
    for(int i = 1; i <= n; i++) {
        int y; cin>>y;
        a[y].x = 1;
        b[N - y].x = 1;
    }
    while((1 << bit) < 2 * N + 1) bit++;
    tot = 1 << bit;
    for(int i = 0; i < tot; i++)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    fft(a, 1), fft(b, 1);
    for(int i = 0; i < tot; i++) a[i] = a[i] * b[i];
    fft(a, -1);
    for(int i = 0; i < (N << 1) - 10; i++){
        int now = (int)(a[i].x/tot+0.5);
        if(now > 0) vis[(int)abs(i-N)] = 1;
    }
    for(int i = n; i < delta + 1; i++){
        if(check(i)) {
            cout<<i<<endl;
            return 0;
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值