2021牛客暑期多校训练营1 Hash Function

H Hash Function

题目链接

题目描述

H题面

给定一个存在n个非负整数的数组S,找出一个最小的模数mod,使得S中任意一个数字取模的结果都不相同。

意思等同于:找出一个最小的mod,使得Hash不会产生冲突。

思路

暴力计算是复杂度是 n 2 n^2 n2显然不行。

对于任意的i, j: S i   %   m o d = a i ;   S j   %   m o d = a j S_i\ \% \ mod = a_i; \ S_j\ \% \ mod = a_j Si % mod=ai; Sj % mod=aj

如果使得 a i ≠ a j a_i \neq a_j ai=aj ∣ S i − S j ∣   %   m o d ≠ 0 |S_i-S_j|\ \%\ mod \neq 0 SiSj % mod=0,即mod不能是 数组中任意两个数差的绝对值 的约数。

这里需要考虑如何用更低的复杂度求出 某个值是否为数组中任意两个数差的绝对值

  • 先说一下如何快速求出 某个值是否为数组中任意两个数的和

    假设 n = 3 n = 3 n=3 S = { 1 , 2 , 4 } S=\{1, 2, 4\} S={1,2,4}

    多项式A为: A ( x ) = x 1 + x 2 + x 4 A(x) = x^1+x^2+x^4 A(x)=x1+x2+x4;多项式B为: B ( x ) = x 1 + x 2 + x 4 B(x) = x^1+x^2+x^4 B(x)=x1+x2+x4

    多项式AB相乘(卷积):

C ( x ) = A ( x ) ∗ B ( x ) = x 1 ∗ ( x 1 + x 2 + x 4 ) + x 2 ∗ ( x 1 + x 2 + x 4 ) + x 4 ∗ ( x 1 + x 2 + x 4 ) = x 2 + 2 ∗ x 3 + x 4 + 2 ∗ 2 5 + 2 ∗ 2 6 + x 8 \begin{aligned} C(x) &= A(x)*B(x) \\ &= x^1*(x^1+x^2+x^4)+x^2*(x^1+x^2+x^4)+x^4*(x^1+x^2+x^4) \\ &= x^2+2*x^3+x^4+2*2^5+2*2^6+x^8 \end{aligned} C(x)=A(x)B(x)=x1(x1+x2+x4)+x2(x1+x2+x4)+x4(x1+x2+x4)=x2+2x3+x4+225+226+x8

​ 可以发现,[2,3,4,5,6,8]为任意 S i + S j S_i+S_j Si+Sj的可能值(i可以等于j)。

​ 但是暴力去相乘的话,复杂度还是 n 2 n^2 n2,所以需要通过FFT/NTT来对卷积操作进行加速(复杂度是O(nlogn))。

  • 求 某个值是否为数组中任意两个数差的绝对值 的操作和上述操作基本相同。

    只需要让多项式B表为: B ( x ) = x − 1 + x − 2 + x − 4 B(x) = x^{-1}+x^{-2}+x^{-4} B(x)=x1+x2+x4

    但是因为存在负值不好处理,需要加上一个偏移量P=6(P自己定义就行了),将多项式转变为 B ( x ) = x 5 + x 4 + x 2 B(x) = x^{5}+x^{4}+x^{2} B(x)=x5+x4+x2

    多项式AB相乘(卷积):

C ( x ) = A ( x ) ∗ B ( x ) = x 1 ∗ ( x 5 + x 4 + x 2 ) + x 2 ∗ ( x 5 + x 4 + x 2 ) + x 4 ∗ ( x 5 + x 4 + x 2 ) = x 3 + x 4 + x 5 + 3 ∗ 2 6 + 2 7 + x 8 + x 9 \begin{aligned} C(x) &= A(x)*B(x) \\ &= x^1*(x^5+x^4+x^2)+x^2*(x^5+x^4+x^2)+x^4*(x^5+x^4+x^2) \\ &= x^3+x^4+x^5+3*2^6+2^7+x^8+x^9 \end{aligned} C(x)=A(x)B(x)=x1(x5+x4+x2)+x2(x5+x4+x2)+x4(x5+x4+x2)=x3+x4+x5+326+27+x8+x9

​ 将得到的数组减去偏移量P取绝对值,[0,1,2,3]为任意 S i + S j S_i+S_j Si+Sj的可能值(i可以等于j)

得到数组中任意两个数差的绝对值的所有值之后,只需要从小到大遍历mod可能的值就行了。

AC代码

#include<bits/stdc++.h>
using namespace std;
const double PI = acos(-1);
const int P = 500001;// 偏移量
const int N = 1<<21;
int limit, bit;
bool vis[N];
int R[N];

// 复数的板子
struct Complex {
	double x, y;
	Complex (double x = 0, double y = 0) : x(x), y(y) { }
} a[N], b[N];

Complex operator * (Complex J, Complex Q) {
	return Complex(J.x * Q.x - J.y * Q.y, J.x * Q.y + J.y * Q.x);
}
Complex operator - (Complex J, Complex Q) {
	return Complex(J.x - Q.x, J.y - Q.y);
}
Complex operator + (Complex J, Complex Q) {
	return Complex(J.x + Q.x, J.y + Q.y);
}


//FFT板子
void FFT(Complex * A, int type) {
	for(int i = 0; i < limit; ++ i)
		if(i < R[i])
			swap(A[i], A[R[i]]);
	for(int mid = 1; mid < limit; mid <<= 1) {
		Complex wn(cos(PI / mid), type * sin(PI / mid));
		for(int len = mid << 1, pos = 0; pos < limit; pos += len) {
			Complex w(1, 0);
			for(int k = 0; k < mid; ++ k, w = w * wn) {
				Complex x = A[pos + k];
				Complex y = w * A[pos + mid + k];
				A[pos + k] = x + y;
				A[pos + mid + k] = x - y;
			}
		}
	}
	if(type == 1) return ;
	for(int i = 0; i <= limit; ++ i)
		a[i].x /= limit, a[i].y /= limit;
}

bool check(int x) {
	for (int i = x; i <= P; i += x) {
		if (vis[i] == true) return false;
	}
	return true;
}

int main() {
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin.exceptions(ios::badbit | ios::failbit);
	int n, tmp;
	cin>>n;
	for(int i = 0; i < n; i++) {
		cin>>tmp;
		a[tmp].x = 1;
		b[P - tmp].x = 1;
	}
	bit = 20;// 只要大于2*p+1就行
	limit = 1 << bit;
	// 预处理
	for(int i = 0; i < limit; i++)
		R[i] = (R[i >> 1] >> 1) | ((i & 1) << (bit - 1));
	// =================================== 
	FFT(a, 1);
	FFT(b, 1);
	for(int i = 0; i < limit; i++) {
		a[i] = a[i] * b[i];
	}
	FFT(a, -1);
	// ===================================
	for (int i = 0; i < limit; i++) {
		int x = (int)(a[i].x + 0.5);
		if(x > 0)
			vis[abs(i - P)] = 1;
	}

	for(int i = n; i < P+1; i++) {
		if(check(i)) {
			cout<<i<<endl;
			break;
		}
	}
	return 0;
}

补充

FFT通过将多项式的系数表示转变成点值表示,再用两个多项式的点值表示相乘,将得到的点值表示逆向转变成系数表式,最后得到的表达式就是卷积之后的多项式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值