鱼贯而入(Miller Rabin)(Pollard Rho)(哈希)

鱼贯而入

题目大意

给你一些数,然后要你把它们放进一个哈希表里面。
要你选一个哈希表的大小,使得每个数在哈希表发现没有位置向后移动放置位置的次数尽可能的多。
输出这个最大的次数。

思路

首先我们考虑一个问题,假设我们枚举了哈希表的大小,如何用一种方法快速地求出次数。
如果长度小,我们可以用哈希暴力枚举搞。
如果大了,我们可以用另一个哈希把下表给离散。
然后就是哈希里面再套一个哈希。

然后你考虑要丢那些数进去算。
首先有一些数根本就不会发生重叠,那我们可以把这些数给排除掉。
那如果 \(x,y\) 在长度为 \(len\) 的哈希表中要有重叠,那它们模 \(len\) 余数要相同,就是 \(len|\ |x-y|\)
那枚举每两个数之间的差,然后把它们的约数全部丢进来看一次。

但你发现你求约数个数太多,而且你求的过程就会炸。
考虑先处理约数个数太多的问题。
那你考虑 \(len\)\(xlen\),发现 \(len\) 一定会有 \(xlen\) 的寻址时间。
那就是说,如果这两个都可以,那 \(len\) 一定更优。
那我们就只需要找这些因子:它大于等于 \(n\),但它除去它的最小质因子小于 \(n\)

那我们考虑有哪些满足,那大于等于 \(n\) 的质数一定可以,而且剩下有可能的一定会在 \(n\sim n^2\) 中。
为什么呢?那我们假设它除去它最小质因子尽可能小,那到最小就是它可以表示成 \(x^2\) 形式,那它除去它的的最小质因子还是它的最小质因子,那这个数 \(x\) 被规定小于 \(n\),那就得到 \(x<n\)
\(x^2<n^2\),所以这个数不过超过 \(n^2\)
那就知道要找那些数了,先是 \(n\sim n^2\) 的直接一股脑全部丢进去,反着才三万多,不多不多。
接着是讲每个 \(|a_i-a_j|\) 质因数分解得出来大于 \(n\) 的质数。
接着就是怎么分解出质因数。
首先可能会有重复的,跟前面一样拿哈希判断。
然后质因数分解用 Miller Rabin 加 Pollard Rho 就可以了。

代码

#include<cstdio>
#include<cstdlib>
#include<iostream>
#define ll long long
#define Mo 1145141

using namespace std;

int tp, n, v[201], ans, sum, pl[201], nn;
ll a[201], h[1145141];
ll hs[2][1145141], ask[200001];

ll Abs(ll x) {
	return x < 0 ? -x : x;
}

ll gcd(ll x, ll y) {
	ll tmp;
	while (y) {
		tmp = x;
		x = y;
		y = tmp % y;
	}
	return x;
}

ll ksc(ll x, ll y, ll mo) {
	x %= mo; y %= mo;
	ll c = (long double)x * y / mo;
	long double re = x * y - c * mo;
	if (re < 0) re += mo;
		else if (re >= mo) re -= mo;
	return re;
}

ll ksm(ll x, ll y, ll mo) {
	ll re = 1;
	while (y) {
		if (y & 1) re = ksc(re, x, mo);
		x = ksc(x, x, mo);
		y >>= 1;
	}
	return re;
}

int add(ll now, ll *hash) {
	int x = now % Mo;
	while (hash[x] && hash[x] != now) {
		x++;
		if (x == Mo) now = 0;
	}
	return x;
}

//快速求答案
void addfish(int i, ll p) {
	ll x = a[i] % p;
	int y = add(x + 1, hs[0]);
	while (hs[0][y] && hs[1][y] != a[i]) {
		x++; if (x == p) x = 0;
		y = add(x + 1, hs[0]);
		sum += v[i];
	}
	hs[0][y] = x + 1; hs[1][y] = a[i];
	pl[i] = y;
}

//Miller Rabin 判断质数
bool mr(ll x, ll p) {
	if (ksm(x, p - 1, p) != 1) return 0;
	ll y = p - 1, z;
	while (!(y & 1)) {
		y >>= 1;
		z = ksm(x, y, p);
		if (z != 1 && z != p - 1) return 0;
		if (z == p - 1) return 1;
	}
	return 1;
}

bool prime(ll now) {
	if (now < 2) return 0;
	if (now == 2 || now == 3 || now == 5 || now == 7 || now == 43) return 1;
	return mr(2, now) && mr(3, now) && mr(5, now) && mr(7, now) && mr(43, now);
}

//Pollard Rho 算法
ll pro(ll p) {
	ll x, y, c, z, g;
	int i, j;
	while (1) {
		x = y = rand() % p;
		z = 1;
		c = rand() % p;
		i = 0; j = 1;
		while (++i) {
			x = (ksc(x, x, p) + c) % p;
			z = ksc(z, Abs(y - x), p);
			if (x == y || !z) break;
			if (!(i % 127) || i == j) {
				g = gcd(z, p);
				if (g > 1) return g;
				if (i == j) {
					y = x;
					j <<= 1;
				} 
			}
		}
	}
}

void work(ll now) {
	if (now < nn) return ;//小优化,因为我们只找不小于 n 的质因子
	int x = add(now, h);//用一个哈希表去重
	if(h[x]) return ; h[x] = now;
	if (prime(now)) {
		ask[++ask[0]] = now;
		return ;
	}
	ll l = pro(now);
	while (now % l == 0) now /= l;
	work(l); work(now);
}

int main() {
//	freopen("hash.in", "r", stdin);
//	freopen("hash.out", "w", stdout);
	
	srand(19491001);
	
	scanf("%d %d", &tp, &n);
	nn = n;
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		v[i] = 1;
		for (int j = 1; j < i; j++)
			if (a[j] == a[i]) {
				v[j]++;
				n--;
				i--;
				break;
			}
	}
	
	for (int i = nn; i <= nn * nn; i++)//n~n*n 全部检验
		ask[++ask[0]] = i;
	for (int i = 1; i <= n; i++)//然后把每个相差的丢进去全部看一遍
		for (int j = 1; j < i; j++)
			work(Abs(a[j] - a[i]));
	
	for (int i = 1; i <= ask[0]; i++) {
		ll now = ask[i];
		sum = 0;
		
		for (int j = 1; j <= n; j++)
			addfish(j, now);
		for (int j = 1; j <= n; j++)
			hs[0][pl[j]] = 0, hs[1][pl[j]] = 0;
		
		ans = max(ans, sum);
	}
	
	printf("%d", ans);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}
posted @ 2021-08-24 09:04  あおいSakura  阅读(69)  评论(0编辑  收藏  举报