【jzoj 6468】鱼贯而入(Miller Rabin)(Pollard Rho)(哈希)

168 篇文章 0 订阅
21 篇文章 0 订阅

鱼贯而入

题目链接:jzoj 6468

题目大意

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

思路

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

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

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

那我们考虑有哪些满足,那大于等于 n n n 的质数一定可以,而且剩下有可能的一定会在 n ∼ n 2 n\sim n^2 nn2 中。
为什么呢?那我们假设它除去它最小质因子尽可能小,那到最小就是它可以表示成 x 2 x^2 x2 形式,那它除去它的的最小质因子还是它的最小质因子,那这个数 x x x 被规定小于 n n n,那就得到 x < n x<n x<n
x 2 < n 2 x^2<n^2 x2<n2,所以这个数不过超过 n 2 n^2 n2
那就知道要找那些数了,先是 n ∼ n 2 n\sim n^2 nn2 的直接一股脑全部丢进去,反着才三万多,不多不多。
接着是讲每个 ∣ a i − a j ∣ |a_i-a_j| aiaj 质因数分解得出来大于 n n 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值