2021牛客暑期多校训练营1 H.Hash Function
https://ac.nowcoder.com/acm/contest/11166/H
题意:
给n个数,让你找出一个最小的正整数m满足:这n个数%m得到的值各不相同。0<=a[i]<=500000。1<=n<=500000。
官方题解:
两个数a,b。若他们%m后余数相同,则a%m=b%m,可得(a-b)%m==0。题目要求各个数余数不同,那么就要任意两个数的差d,d%m!=0。也就是上述的找到最小的m,使其不是任意两个数的差的约数。
如果能知道任意两个数的差的集合,那么就可以直接枚举答案来判断,m是不是某个差的约数,也就是用d%m!=0来判断m是不是符合要求。
要求任意两个数的差的集合,直接暴力枚举的话,复杂度是O(n2)。考虑把他转换成多项式乘法的形式。
若存在的数集合为s,那么把他取反后得到的集合称为-s。
系数为1表示存在,指数为i表示当前的数为i(相当于把题目的数放在多项式的指数里面)。
把集合s用多项式表示有A(x)={p0+p1X+p2X2....},pi=1表示存在数i。同理,
取反后的集合-s用多项式表示有B(x)={p500000-0X500000-0+p500000-1X500000-1+p500000-2X500000-2....}。因为数组不能存负数,所以加上偏移量500000。
之后考虑A(x)*B(x),A(x)的每一项都与B(x)的每一项系数相乘,指数相加。系数相乘为1表示两个数都存在,指数相加得到的和就是两个数的差+偏移量。乘法就是任意两项都相乘,所以一次乘法就能得到任意两数的差组成的多项式。
多项式乘法用FFT加速,之后枚举答案。
代码:
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include<bits/stdc++.h> using namespace std; const int N = 1500700; const int MX = 500003;//偏移量要略大于5e5 const double PI = acos(-1); 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[N], b[N]; struct FFT { int rev[N], bit=0, tot=0; 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) { auto w1 = Complex({cos(PI / mid), inv * sin(PI / mid)}); for (int i = 0; i < tot; i += mid * 2) { auto wk = Complex({1, 0}); for (int j = 0; j < mid; j ++, wk = wk * w1) { auto x = a[i + j], y = wk * a[i + j + mid]; a[i + j] = x + y, a[i + j + mid] = x - y; } } } } void work(){ int n=MX,m=MX; while ((1 << bit) < n + m + 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 + m; i ++ ) a[i].x = (int)(a[i].x / tot + 0.5); } }; int p[N],vis[N]; int main() { int n;cin>>n; for(int i=1;i<=n;i++) cin>>p[i]; for(int i=1;i<=n;i++){ a[p[i]].x=1;//+p[i] b[MX-p[i]].x=1;//-p[i] } struct FFT solve;solve.work();//p[i]和-p[i]乘积 for(int i=0;i<=MX;i++){ vis[i]=a[i+MX].x;//得到任意出现的差值 } int ans; for(int i=1;i<=MX;i++){//枚举答案i bool ok=1; for(int j=i;j<=MX;j+=i){ //枚举i倍数,只要i不是任意差值的约数,i就是答案 if(vis[j]){ ok=0;break; } } if(ok){ printf("%d\n",i); break; } } return 0; }