2021牛客暑期多校训练营1 H - Hash Function (套路构造+ fft/ntt加速多项式乘法)
题意:
一个长度为\(n,n\le500000\)的数列,其中的元素\(0\le a_i\le500000\),现在让你找到一个最小的正整数\(x\)使得,\(i\in[1,n],a_i\%x\)的值,全部唯一。
思路:
假如有两个位置\(i < j\),\(a_i \%x = a_j\%x\),即\(|a_i-a_j|\%x=0\),如果我们能确定这个数列所能产生的所有的差值,那么一个合法的\(x\)即代表对于所有的存在的差值来说\(x\)都不是他们的因子。
我们从\(1-n\)枚举\(i\),然后枚举这个数的倍数,如果这个数存在,那么\(i\)即不合法。
过程是一个经典的调和级数的过程。
现在我们的问题就剩如何快速的获得所有可能的差值,这里有一个可能比较套路的构造。
考虑构造一个多项式\(A(x) = \sum_{i=0}^{i=n}f_ix^i\),其中\(i\)代表数组当前这项代表的是\(i\)这个数,\(f_i\)表示这个数\(i\)是否在原序列存在。
另外构造一个多项式\(B(x) = \sum_{i=50000-0}^{i=0}f_i'x^i\),为什么是\(500000-i\)其实应该是\(-i\),这样两个序列相乘,只要当前指数\(i\)的系数不为0,即代表原序列中有两个数可以相减得到这个数(因为基本单元就是两个系数相加获得),但是相减可能是负数(考虑数组下标无法为负),所以我们加上一个数组最大取值的偏移量,这样就获得了一个卷积形式的式子。
\(ans(n) = \sum_{i=0}^{n}f_{i}f_{x-i}'x^n\),\(ans_n\)代表\(n\)这一项的系数。
这个东西可以用\(A(x)\)和\(B(x)\)两个多项式相乘得到,用\(FFT/NTT\)加速这一过程即可。
到此我们记录所有出现过的差值,即\(ans_i>0\)的\(i\),然后用开始提到的方法枚举判断即可。
时间复杂度:\(O(nlogn)\)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const db PI = acos(-1.0);
const int N = 2e6 + 1000;
const int bs = 500000;
const ll G = 3;
const ll MOD = 998244353;
ll a[N],n,n_m = 1,in[N],iin[N];
ll ksm(ll a,ll b) {
ll res = 1;
while(b) { if(b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; }
return res;
}
void NTT(ll *A,int type) {
int k = log2(n_m);
for(int i = 0;i < n_m;i ++) {
int t = 0;
for(int j = 0;j < k;j ++) {
if(i&(1<<j)) t |= 1<<(k-j-1);
}
if(i < t) swap(A[i],A[t]);
}
for(int mid = 1;mid < n_m;mid <<= 1) {
ll wn = ksm(G,(MOD-1)/(mid<<1));
if(type == -1) wn = ksm(wn,MOD-2);//因为是曲目所以逆变换取逆元
for(int len = mid << 1,j = 0;j < n_m;j += len) {
ll w = 1;
for(int k = 0;k < mid;k ++,(w *= wn)%=MOD) {
ll x = A[k+j],y = w * A[k+j+mid] % MOD;
A[k+j] = (x + y) % MOD;
A[k+j+mid] = (x - y + MOD) % MOD;
}
}
}
}
ll ans[N];
bool vis[N];
//套路构造多项式 x的指数代表这个数的真实值 系数代表这个数是否存在 另一个序列对应位置取负加上一个偏移量确保下标为正数
//即变为卷积的形式 故可用fft/ntt加速多项式乘法 从nlogn的获得所有可能的差值
//对于每个数 如果他的倍数存在 那么这个数就是不合法的 故nlogn(调和级数)可完成这个工作
void solve() {
scanf("%d",&n);
ll ma = 0;
for(int i = 1;i <= n;i ++) {
scanf("%d",&a[i]);
in[a[i]]= 1; iin[-a[i]+bs] = 1;
ma = max({ma,a[i],-a[i]+bs});
}
while(n_m <= (ma+1) * 2) n_m <<= 1;
NTT(in,1); NTT(iin,1);
for(int i = 0;i < n_m;i ++) in[i] = in[i] * iin[i] % MOD;
NTT(in,-1);
for(int i = 0;i < n_m;i ++) ans[i] = in[i] * ksm(n_m,MOD-2) % MOD;
for(int i = 0;i < n_m;i ++) {
if(ans[i] != 0) {
int tmp = (i > bs ? i - bs : bs - i);
if(tmp == 0 || vis[tmp]) continue;
vis[tmp] = true;
}
}
int mi = 1;
for(int i = 1;i <= bs;i ++) {
for(int j = i;j <= bs;j += i) {
if(vis[j]) if(mi == i) mi++;
}
}
printf("%d\n",mi);
}
int main() {
solve();
return 0;
}