1. 程式人生 > 其它 >魚貫而入(Miller Rabin)(Pollard Rho)(雜湊)

魚貫而入(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;
}