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

题目

正解

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

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

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


代码

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

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

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

using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#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=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 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。

你可能感兴趣的:(6468. 【GDOI2020模拟02.09】鱼贯而入)