【高维前缀和-分块优化】BZOJ5382走路-Walk

【题目】
原题地址
给一棵树,每个节点有一个权值 v a l
如果两个点 a b 满足 a b 的祖先且 v a l [ b ] v a l [ a ] 的约数,那么可以从 a 一步跳到 b
求从1号节点走到各每个节点的路径数。
n <= 10 5 , v a l [ i ] <= 10 18 ,保证对于任意节点 i v a l [ i ] v a l [ 1 ] 的约数。

【题目分析】
woc这个权值这么大,又要打Miller_Rabin&Pollard_Rho了,心态爆炸。

【解题思路】
10 18 大约有 10 5 个。
一个显然的暴力是:对 v a l [ 1 ] 分解质因数,求出其所有约数,用一个数组记录每个约数。dfs一遍这棵树,对于每个节点,枚举 v a l [ 1 ] 的所有约数看是否是自己的倍数,并对 f 进行贡献,然后再把这个点的 f 贡献到数组中。

约数启发我们在质因数方面下手,一个显然的结论是:一个数 A 是另一个数 B 的约数,充要条件是 A 的所有种类质因子数 <= B 对应质因子数。
因此我们可以将每一个数看成一个[约数]维的向量,接下来我们要处理的其实就是一个高维前缀和。
如何理解高维前缀和?我们先从二维开始考虑。
假设一个数为 2 x 3 y ,那么用向量 ( x , y ) 表示,在二维矩阵中,如果要查询比它小的向量可以采用下面的方法:
1:单点修改 O ( 1 ) ,矩形查询左上角向量值之和 O ( n m )
2:矩形修改右下角所有向量值 O ( n m ) ,单点查询 O ( 1 )
两种都不能满足我们的要求,这启发我们均摊复杂度。
可以考虑下面一种方法:对于每一个向量 ( x , y ) ,我们前缀和记录所有 x 和它一样, y 小于等于它的向量值之和。这样对于每次查询,我们只需要固定 y ,扫描 1 > x ;对于修改,我们固定 x ,扫描 y > m 。就像下面这样。
这里写图片描述

考虑拓展到高维,我们发现每一维修改查询时间和向量最大值有关,启发我们将向量分成两个尽可能相等的组。如果一个数分解为 a 1 p 1 a 2 p 2 a x p x ,那么每一维度的大小应该分成 p 1 p 2 . . . p x ,这个数大概是 10 3 ,分块以后下标的处理就是将这块里的 p i + 1 都乘起来什么的。

这样查询和修改复杂度就是 10 3 ,再乘上点数时间复杂度就 O ( 1 e 8 ) 了,需要卡常。当然如果有偏重的话两个块大小可能会不平衡,这时就需要更玄学的卡常了,也许我们可以再写一个程序均等分配

【参考代码】

#include<bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;

typedef long long LL;
const int N=1e5+10;
const int M=5005;
const int S=15;
const int mod=1e9+7;
int n,tot,head[N],fa[N];
LL val[N],f[N];

LL read()
{
    LL ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) {ret=ret*10ll+(c^48);c=getchar();}
    return f?ret:-ret;
}

void write(LL x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar((x%10)^48);
}

struct Tway
{
    int v,nex;
};
Tway e[N<<1];

void add(int u,int v)
{
    e[++tot]=(Tway){v,head[u]};head[u]=tot;
    e[++tot]=(Tway){u,head[v]};head[v]=tot;
}

void up(LL &x,LL y)
{
    x+=y;
    if(x>mod) x-=mod;
}

struct PRIME
{
    LL top,stk[S*10],cnt[S*10];

    LL gcd(LL x,LL y)
    {
        if(x==0) return 1;
        if(x<0) return gcd(-x,y);
        while(y) {LL t=x%y;x=y;y=t;}
        return x;
    }

    LL mult_mod(LL a,LL b,LL c)  
    {  
        a%=c;b%=c;  
        LL ret=0;  
        while(b)  
        {  
            if(b&1) {ret+=a;ret%=c;}  
            a<<=1;b>>=1;  
            if(a>=c) a%=c; 
        }  
        return ret;  
    }   

    LL pow_mod(LL x,LL y,LL mod)  
    {  
        if(y==1)
            return x%mod;  
        x%=mod;LL tmp=x,ret=1;  
        while(y)  
        {  
            if(y&1) 
                ret=mult_mod(ret,tmp,mod);  
            tmp=mult_mod(tmp,tmp,mod);  
            y>>=1;  
        }  
        return ret;  
    }  

    bool check(LL a,LL n,LL x,LL t)  
    {  
        LL ret=pow_mod(a,x,n);  
        LL last=ret;  
        for(LL i=1;i<=t;i++)  
        {  
            ret=mult_mod(ret,ret,n);  
            if(ret==1 && last!=1 && last!=n-1) 
                return true; 
            last=ret;  
        }  
        if(ret!=1) return true;  
        return false;  
    }  

    bool Miller_Rabin(LL n)  
    {  
        if(n<2) return false;  
        if(n==2) return true;  
        if(!(n&1)) return false; 
        LL x=n-1,t=0;  
        while(!(x&1)) {x>>=1;t++;}  
        for(LL i=0;i<S;i++)  
        {  
            LL a=rand()%(n-1)+1; 
            if(check(a,n,x,t))  
                return false;
        }  
        return true;  
    }  

    LL Pollard_rho(LL x,LL c)  
    {  
        LL i=1,k=2,x0=rand()%x,y=x0;  
        while(true)  
        {  
            i++;  
            x0=(mult_mod(x0,x0,x)+c)%x;  
            LL d=gcd(y-x0,x);  
            if(d!=1 && d!=x) return d;  
            if(y==x0) return x;  
            if(i==k) {y=x0;k+=k;}  
        }  
    }  

    void findfac(LL n)  
    {  
        if(Miller_Rabin(n))
        {  
            stk[++top]=n;cnt[top]=0;
            return;  
        }  
        LL p=n;  
        while(p>=n)
            p=Pollard_rho(p,rand()%(n-1)+1);  
        findfac(p);findfac(n/p);  
    }  

    void work(LL x)
    {
        top=0;findfac(x);int t=top;
        sort(stk+1,stk+top+1);
        top=0;stk[0]=-1;
        for(int i=1;i<=t;++i)
        {
            if(stk[i]^stk[top])
                stk[++top]=stk[i],cnt[top]=0;
            cnt[top]++;
        }
    } 
};

struct Divide
{
    int mid;
    PRIME pi;
    LL sum[M][M];

    void init(LL x)
    {
        pi.work(x);
        mid=pi.top>>1;
    }

    void modify(int x,int idl,int idr,LL v,int p)
    {
        if(p==mid+1)
        {
            up(sum[idl][idr],v);
            return;
        }
        int cs=0;
        while(x/pi.stk[p]*pi.stk[p]==x)
            x/=pi.stk[p],++cs;
        for(int i=cs;~i;--i)
            modify(x,idl*(pi.cnt[p]+1)+i,idr,v,p+1);
    }

    void update(LL x,LL v)
    {
        int idr=0;
        for(int i=mid+1;i<=pi.top;++i)
        {
            int cs=0;
            while(x/pi.stk[i]*pi.stk[i]==x)
                x/=pi.stk[i],++cs;
            idr=idr*(pi.cnt[i]+1)+cs;
        }
        modify(x,0,idr,v,1);
    }

    LL query(LL x,int idl,int idr,int p)
    {
        if(p==pi.top+1)
            return sum[idr][idl];
        int cs=0;LL ret=0;
        while(x/pi.stk[p]*pi.stk[p]==x)
            x/=pi.stk[p],++cs;
        for(int i=cs;i<=pi.cnt[p];++i)
            up(ret,query(x,idl*(pi.cnt[p]+1)+i,idr,p+1));
        return ret;
    }

    LL getans(LL x)
    {
        int idr=0;
        for(int i=1;i<=mid;++i)
        {
            int cs=0;
            while(x/pi.stk[i]*pi.stk[i]==x)
                x/=pi.stk[i],++cs;
            idr=idr*(pi.cnt[i]+1)+cs;
        }
        return query(x,0,idr,mid+1);
    }
}A;

void dfs(int x)
{
    if(x^1)
        f[x]=A.getans(val[x]);
    A.update(val[x],f[x]);
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(v^fa[x])
            fa[v]=x,dfs(v);
    }
    A.update(val[x],mod-f[x]);
}

int main()
{
    freopen("walk.in","r",stdin);
    freopen("walk.out","w",stdout);

    srand(19260817);
    n=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        add(u,v);
    }
    for(int i=1;i<=n;++i)
        val[i]=read();
    A.init(val[1]);
    f[1]=1;dfs(1);
    for(int i=1;i<=n;++i,putchar('\n'))
        write(f[i]);

    return 0;
}

【总结】
高维前缀和是一个好东西,分块优化复杂度也是一个很好的思想(其实是均摊复杂度啦)。

猜你喜欢

转载自blog.csdn.net/dream_lolita/article/details/80792282
今日推荐