HDU5599GTW likes tree

题目
题意:给出一棵点有权的树,求 i,jgcd(val[i],....val[j]) ,就是求所有路径gcd的乘积,保证 1<=val[i]<=105

分析:一个简单的思路就是暴力树dp,开一个vector记录每个点向下连出去的gcd的情况,通过前缀积的做法可以做到 O(nfac) ,然而比较难写,而且空间未必能开得下。
另外一个做法就是点分治,但是复杂度并不优秀。
考虑每个质因子对答案的贡献,由于 val[i]<=105 一个数不同的质因数不会超过6个.从大到小枚举质因子的幂次,由于我们只关心每个连通块的大小,只需要用并查集维护即可。

#include<bits/stdc++.h>
using namespace std;
const int Maxn=200020,M=1e9+7,N=100020;
typedef pair<int,int>pi;
int isp[N],cnt_pri,pri[N];
vector<int>yinzi[N];
vector<int>G[Maxn];
vector<pi>pro[N];
int n,tl;
int is[Maxn],id[Maxn],ct[22];
int f[Maxn],sz[Maxn],in[Maxn];
int find(int x)
{
    int a;
    for(a=x;f[a]!=a;a=f[a]);
    for(;f[x]!=a;)
    {
        int t=f[x];f[x]=a;
        x=t;
    }
    return a;
}
int powmod(int x,int y)
{
    int ret=1;
    while(y)
    {
        if(y&1)ret=1LL*ret*x%M;
        y>>=1;
        x=1LL*x*x%M;
    }
    return ret;
}
int solve()
{
    int ret=1;
    for(int it=0;it<cnt_pri;it++)
    {
        int cur=0;
        int p=pri[it];
        if(pro[p].empty())continue;
        tl++;
        for(int i=0;i<pro[p].size();i++)
        {
            int u=pro[p][i].second,w=pro[p][i].first;
            if(is[u])continue;
            cur=(cur+w)%(M-1);
            sz[u]=1;f[u]=u;
            in[u]=tl;
            for(int j=0;j<G[u].size();j++)
            {
                int v=G[u][j];
                if(in[v]<tl)continue;
                int uu=find(u),vv=find(v);
                cur+=1LL*sz[uu]*sz[vv]%(M-1)*w%(M-1);
                if(cur>=M-1)cur-=M-1;
                if(sz[uu]>sz[vv]){f[vv]=uu;sz[uu]+=sz[vv];}
                else{f[uu]=vv;sz[vv]+=sz[uu];}
            }
        }
        ret=1LL*ret*powmod(p,cur)%M;
    }
    return ret;
}
int main()
{
    for(int i=2;i<N;i++)
    {
        if(!isp[i])
        {
            for(int j=i;j<N;j+=i)
            {
                yinzi[j].push_back(i);
                isp[j]=1;
            }
            pri[cnt_pri++]=i;
        }
    }
    int _;scanf("%d",&_);
    while(_--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            G[i].clear();
            is[i]=0;
        }
        for(int i=1;i<n;i++)
        {
            int u,v;scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        for(int i=0;i<cnt_pri;i++)pro[pri[i]].clear();
        for(int i=1;i<=n;i++)
        {
            int x;scanf("%d",&x);
            for(int j=0;j<yinzi[x].size();j++)
            {
                int cnt=0,tp=x,p=yinzi[x][j];
                while(tp%p==0)cnt++,tp/=p;
                pro[p].push_back(pi(cnt,i));
            }
        }
        for(int i=0;i<cnt_pri;i++)
            sort(pro[pri[i]].begin(),pro[pri[i]].end(),greater<pi>());
        int ans1=solve();
        int m;scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int x;scanf("%d",&x);
            is[x]=1;
        }
        int ans2=solve();
        ans1=1LL*ans1*powmod(ans2,M-2)%M;
        printf("%d\n",ans1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值