6468. 【GDOI2020模拟02.09】鱼贯而入

题目

正解

比赛时几乎没有怎么思路。
其实这个东西应该很显然:如果要让答案大于$0¥,那么至少存在两个会有冲突,也就是说在模了之后至少有两个不同的数的位置会重叠。
所以只需要枚举两两之间的差的约数,都试一遍就可以了。

这样当然会TLE。
考虑某个数 p p p以及它的倍数 k p kp kp,显然,在 k p kp kp中撞上的在 p p p中一定也能撞上,所以如果 p p p符合条件,就不用算 k p kp kp了。
所以,对于某个数 x x x,求出它的最小质因子 y y y,只有 x y < n \frac{x}{y}<n yx<n的时候才有必要计算 x x x
按照这个性质,可以进一步推出:要么 x x x是素数,否则 x ∈ [ n , n 2 ] x\in [n,n^2] x[n,n2]

对于 [ n , n 2 ] [n,n^2] [n,n2]的每个都枚举一遍,借助并查集用接近 O ( n ) O(n) O(n)的时间计算。
对于大质数,用Polllard-Rho方法分解出来之后计算,计算的时候与前面的类似,不过还要打个哈希表来模拟下标。


代码

其实这题说起来容易,但代码实现起来真的有点恶心。
话说Pollard-Rho有个不错的优化,就是不要每次枚举都计算 g c d gcd gcd,乘 128 128 128个之后再求 g c d gcd gcd
为了防止圈过小的情况,还要倍增一下。

然后就是我终于知道了原来long long相乘取模借助long double可以做到几乎没有误差。最重要的是要在相乘之前先让它们对模数取模。

还有,经过实测,x*x+c的伪随机方式似乎带着一种奇怪的魔力。用其它的方法进行伪随机时间会T飞。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <cstdlib>
#include <ctime>
#define N 210
#define ll long long
#define ull unsigned long long
int n;
long long a[N];
int lst[N];
ull ans;
ll mul(ll x, ll y, ll mo) {
    ll z = (long double) x * y / mo;
	z = x * y - z * mo;
    if(z < 0) z += mo; else if(z > mo) z -= mo;
    return z;
}
inline ull qpow(ull x,ull y,ull mo){
	ull res=1;
	for (;y;y>>=1,x=mul(x,x,mo))
		if (y&1)
			res=mul(res,x,mo);
	return res;
}
const int p[9]={2,3,5,7,11,13,17,19,23};
inline bool mr(ull v){
	if (v==0 || v==1)
		return 0;
	for (int i=0;i<9;++i){
		if (v==p[i])
			return 1;
		if (v%p[i]==0)
			return 0;
	}
	ull x=v-1,k=0;
	while (!(x&1))
		x>>=1,++k;
	for (int i=0;i<9;++i){
		ull t=qpow(p[i],x,v);
		if (t==1 || t==v-1)
			continue;
		for (int j=1;j<k;++j){
			t=mul(t,t,v);
			if (t==v-1)
				break;
		}
		if (t!=v-1)
			return 0;
	}
	return 1;
}
inline ull gcd(ull a,ull b){
	while (b){
		ull k=a%b;
		a=b,b=k;
	}
	return a;
}
#define irand (rand()*RAND_MAX+rand())
#define lrand (irand*RAND_MAX*RAND_MAX+irand)
inline ull rho(ull n){
	while (1){
		ull a=lrand%n,c=lrand%n,k=lrand%n,b=a,s=1;
		for (int i=1,k=2;1;++i){
			a=(a*a+c)%n;
//			printf("%llu %llu\n",a,abs((long long)a-(long long)b)); 
			if (a==b)
				break;
			ull t=mul(s,abs((long long)a-(long long)b),n);
			if (t==0)
				return gcd(s,n);
			s=t;
			if (i==k){
				ull g=gcd(s,n);
				if (g!=1)
					return g;
				k<<=1;
				b=a;
				continue;
			}
			if (!(i&127)){
				ull g=gcd(s,n);
				if (g!=1)
					return g;
			}
		}
	}
}
#define SIZE 40010
int fa[SIZE],need[N];
int getfa(int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);}
inline ull calc1(int len){
	for (int i=0;i<len;++i)
		fa[i]=i;
	ull res=0;
	for (int i=1;i<=n;++i){
		if (lst[i]){
			res+=need[i]=need[lst[i]];
			continue;
		}
		int x=a[i]%len;
		getfa(x);
		res+=need[i]=(fa[x]>=x?fa[x]-x:len+fa[x]-x);
		x=fa[x];
		int y=getfa((x+1)%len);
		fa[x]=y;
	}
	return res;
}
#define mo 3001 
ull id[mo],used[N][2];
inline int myhash(ull key){
	int i=key%mo;
	for (;id[i] && id[i]!=key;i=(i+1==mo?0:i+1));
	return i;
}
inline int newnode(ull key){
	int i=myhash(key);
	if (id[i]==0){
		id[i]=key;
		fa[i]=i;
	}
	return i;
}
inline ull calc2(ull len){
	ull res=0;
	for (int i=1;i<=n;++i){
		if (lst[i]){
			res+=need[i]=need[lst[i]];
			continue;
		}
		ull x=a[i]%len,ha=myhash(x);
		if (id[ha]==0){
			id[ha]=x;
			fa[ha]=newnode((x+1)%len);
			used[i][0]=ha;
			used[i][1]=fa[ha];
			need[i]=0;
			continue;
		}
		getfa(ha);
		res+=need[i]=(id[fa[ha]]>=x?id[fa[ha]]-x:len+id[fa[ha]]-x);
		ha=fa[ha];
		x=id[ha];
		int y=getfa(newnode((x+1)%len));
		fa[ha]=y;
		used[i][0]=ha;
		used[i][1]=y;
	}
	for (int i=1;i<=n;++i)
		if (!lst[i])
			id[used[i][0]]=id[used[i][1]]=0;
	return res;
}
set<ull> vis;
void find(ull v){
	if (v<=n*n)
		return;
	if (vis.find(v)!=vis.end())
		return;
	vis.insert(v);
	if (mr(v)){
		ans=max(ans,calc2(v));
		return;
	}
	ull d=rho(v);
	ull u=v/d,g=gcd(d,u);
	if (g!=1){
		d/=g,u/=g;
		find(g);
	}
	find(d),find(u);
}
int main(){
	freopen("hash.in","r",stdin);
	freopen("hash.out","w",stdout);
	srand(time(0));
	int type;
	scanf("%d%d",&type,&n);
	for (int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
		for (int j=i-1;j>=0;--j)
			if (a[i]==a[j]){
				lst[i]=j;
				break;
			}
	}
	for (int i=n;i<=n*n;++i)
		ans=max(ans,calc1(i));
	for (int i=0;i<9;++i)
		if (p[i]>n*n)
			ans=max(ans,calc1(p[i]));
	for (int i=1;i<=n;++i)
		for (int j=i+1;j<=n;++j){
			ull tmp=abs(a[i]-a[j]);
			if (tmp==0)
				continue;
			for (int k=0;k<9;++k)
				while (tmp%p[k]==0)
					tmp/=p[k];
			find(tmp);
		}
	printf("%lld\n",ans);
	return 0;
}

总结

真是一个恶心的程序……
不过,这也让我了解怎么更加优美地打Pollard-Rho。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值