[2018.6.21集训]走路-分块高维前缀和-Pollard-Rho

Posted zltttt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[2018.6.21集训]走路-分块高维前缀和-Pollard-Rho相关的知识,希望对你有一定的参考价值。

题目大意

给一棵树,每个节点有一个权值$val$。
如果两个点$a$和$b$满足$a$为$b$的祖先且$val[b]$为$val[a]$的约数,那么可以从$a$一步跳到$b$。
求从$1$号节点走到各每个节点的路径数。

$n leq 10^5 , val[i] leq 10^{18},$保证对于任意节点$i$,$val[i]$为$val[1]$的约数。

题解

首先有定理:一个数$n$的约数个数大概是$n^{frac{1}{3}}$的级别
然后得到有理有据的一个部分分($val[i] leq 10^9,40$分)算法:
对$val[1]$分解质因数,求出其所有约数,然后开等同于约数个数个桶,每到一个节点就枚举每个约数看是否为自己的倍数,如果是就取出对应桶中的值加入当前点的$dp$值,最后再把当前点也加入桶中。
(事实上对所有$val$进行一次$unique$就可以不用分解质因数了)

复杂度$O(n*10^6)$,显然不能过。

于是来一发神奇的分块高维前缀和。
具体而言,将每个数分解为若干个质因数的乘积,那么就可以将这个数看成一个向量,每一维的值对应其某一个质因数的数量。
将向量从中间划开,分成尽量相等的两组维度,然后对于其中一组维度做前缀和。

也就是说在此题中,假如是一个二维向量$(i,j)$,对应质因数$2^i*3^j$。
此时,将向量划成$i$和$j$两组维度,那么将向量$i,j$的贡献$v$加入的过程如下:

for(int k=i;k<=max_i;k++)
    sum[k][j]+=v

查询向量$(i,j)$的前缀和则用:

for(int k=j;k<=max_j;k++)
    ans+=sum[i][k]

此时查询复杂度和修改复杂度均为$O(sqrt(n))$,$n$为所有维度的$max_i$之积,在这里为$10^6$。

于是复杂度降为$O(n*10^3)=O(10^8)$,玄学卡常一波即可通过~
由于要分解$10^{18}$级别的大数,还需要来一发Pollard-Rho......

代码:

#include<map>
#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N=1e5+9;
const ll md=1e9+7;

inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

inline void write(ll x)
{
    if(x>=10)write(x/10);
    putchar('0'+x%10);
}

int n;
int to[N<<1],nxt[N<<1],beg[N],tot;
int fa[N],id[N],ed[N],seg[N],dfn;
ll val[N],f[N];

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
}

namespace di
{
    ll stk[100],cnt[100],top,c;
    vector<ll> vec,vecs;
    vector<ll> vf;
    map<ll,int> ha;

    inline ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}

    inline ll mul(ll a,ll b,ll p)
    {
        return ((a*b-(ll)((long double)a*b/p)*p)%p+p)%p;
    }

    inline ll qpow(ll a,ll b,ll p)
    {
        ll ret=1;
        while(b)
        {
            if(b&1)ret=mul(ret,a,p);
            a=mul(a,a,p);b>>=1;
        }
        return ret;
    }

    inline bool check(ll a,ll n)
    {
        ll x=n-1,t=0;
        for(;!(x&1);x>>=1)t++;
        x=qpow(a,x,n);
        if(x==1 || x==n-1)return 1;
        x=mul(x,x,n);
        for(int i=1;i<=t;i++,x=mul(x,x,n))
        {
            if(x==n-1)return 1;
            if(x==1)return 0;
        }
        return 0;
    }

    inline bool miller_rabin(ll x)
    {
        if(x==1 || x==2)return 1;
        for(int i=1;i<=15;i++)
            if(!check(rand()%(x-1)+1,x))
                return 0;
        return 1;
    }

    inline void find(ll x);

    inline bool pollard_rho(ll x)
    {
        int cnt=0,k=0;
        ll x1=rand()%x,x2=x1,d;
        while(1)
        {
            x1=(mul(x1,x1,x)+c)%x;
            d=gcd(abs(x1-x2),x);
            if(d!=1 && d!=x)
                return find(x/d),find(d),1;
            if(x1==x2)return 0;
            if((++cnt)==(1<<k))
                k++,x2=x1;
        }
    }

    inline void find(ll x)
    {
        if(miller_rabin(x))
        {
            stk[++top]=x;
            cnt[top]=0;
            return;
        }
        while(!pollard_rho(x))
            c=rand();
    }

    inline void work(ll x)
    {
        top=0;find(x);int e=top;
        sort(stk+1,stk+top+1);
        top=0;stk[0]=-1;
        for(int i=1;i<=e;i++)
        {
            if(stk[i]!=stk[top])
            {
                stk[++top]=stk[i];
                cnt[top]=0;
            }
            cnt[top]++;
        }
    }
    
    inline void dfss(int x,ll v)
    {
        if(x==top+1)
        {
            vec.push_back(v);
            vf.push_back(0);
            ha[v]=vec.size()-1;
            return;
        }

        ll mul=1;
        for(int i=0;i<=cnt[x];i++,mul*=stk[x])
            dfss(x+1,v*mul);
    }
}

using namespace di;

inline void chk(ll &x){if(x>=md)x-=md;}

namespace sbds
{
    const int M=5009;
    int mid;
    ll ds[M][M];

    inline void init(ll x)
    {
        work(x);
        mid=top>>1;
    }

    inline void modifys(ll x,int nid,int id,ll v,int p)
    {
        if(p==mid+1)
        {
            chk(ds[nid][id]+=v);
            return;
        }
        int cnts=0;
        while(x/stk[p]*stk[p]==x)
            x/=stk[p],cnts++;
        for(int i=cnts;i>=0;i--)
            modifys(x,nid*(cnt[p]+1)+i,id,v,p+1);
    }
        
    inline void modify(ll x,ll v)
    {
        int id=0;
        for(int i=mid+1;i<=top;i++)
        {
            int cnts=0;
            while(x/stk[i]*stk[i]==x)
                x/=stk[i],cnts++;
            id=id*(cnt[i]+1)+cnts;
        }
        modifys(x,0,id,v,1);
    }

    inline ll querys(ll x,int nid,int id,int p)
    {
        if(p==top+1)
            return ds[id][nid];
        int cnts=0;ll ret=0;
        while(x/stk[p]*stk[p]==x)
            x/=stk[p],cnts++;
        for(int i=cnts;i<=cnt[p];i++)
            chk(ret+=querys(x,nid*(cnt[p]+1)+i,id,p+1));
        return ret;
    }

    inline ll query(ll x)
    {
        int id=0;
        for(int i=1;i<=mid;i++)
        {
            int cnts=0;
            while(x/stk[i]*stk[i]==x)
                x/=stk[i],cnts++;
            id=id*(cnt[i]+1)+cnts;
        }
        return querys(x,0,id,mid+1);
    }
}

inline void dfs(int u)
{
    if(u!=1)
        f[u]=sbds::query(val[u]);
    sbds::modify(val[u],f[u]);
    for(int i=beg[u];i;i=nxt[i])
        if(to[i]!=fa[u])
            fa[to[i]]=u,dfs(to[i]);
    sbds::modify(val[u],md-f[u]);
}

int main()
{
    srand(514);n=read();
    for(int i=2,u,v;i<=n;i++)
    {
        u=read();v=read();
        add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++)
        val[i]=read();

    sbds::init(val[1]);
    dfs(f[1]=1);
    for(int i=1;i<=n;i++)
        write(f[i]),putchar('
');
    return 0;
}

以上是关于[2018.6.21集训]走路-分块高维前缀和-Pollard-Rho的主要内容,如果未能解决你的问题,请参考以下文章

高维前缀和

NOIP2016提高A组集训第3场10.31高维宇宙

高维前缀和

[2018.3.27集训]Subset-容斥-高维数点

2022牛客寒假算法基础集训营 5 全部题解

2022牛客寒假算法基础集训营 5 全部题解