Codeforces Round #415 (Div. 1) E. Surprise me! 莫比乌斯反演+虚树+换根树形dp

E. Surprise me!

题意:给你一颗带有点权的树,求:(所有点权值不一样,且对于任意 i :ai<=n,这点十分重要)

                                       \frac{1}{n*(n-1)}\sum_{i=1}^{n}\sum_{j=1}^{n}\varphi (ai*bj)dist(i,j)

思路:根据欧拉函数的性质推一下:

                                     \varphi (ai*bj)=\varphi(ai)*\varphi(bj)*\frac{gcd(ai,bj)}{\varphi (gcd(ai,bj))}

先不管前面的n,设ans:

                           ans=\sum_{i=1}^{n}\sum_{j=1}^{n}\varphi(ai)*\varphi(bj)*\frac{gcd(ai,bj)}{\varphi (gcd(ai,bj))}*dist(i,j)

枚举gcd:

                      ans=\sum_{d=1}^{n}\frac{d}{\varphi (d)}\sum_{i=1}^{n}\sum_{j=1}^{n}\varphi (ai)*\varphi (bj)*dist(i,j)[gcd(ai,bj)=d]

设 F(x):

                                F(d)=\sum_{d|x}^{n}\sum_{i=1}^{n}\sum_{j=1}^{n}\varphi (ai)\varphi (bj)dist(i,j)[gcd(ai,bj)=x]

莫比乌斯反演:

                                        ans=\sum_{d=1}^{n}\frac{d}{\varphi (d)}\sum_{d|x}^{n}\mu (\frac{x}{d})F(x)

假设我们已经求好所有的F(x),那么我们可以两层for循环求出答案,因为现在这个式子的复杂度是调和级数。

我们发现影响F(x)的点的权值全都是 x 的倍数,那么我们可以把这些点取出来建虚树,但是对于任意的x,我们都要建虚树,不难证明,所有虚树的总大小为调和级数,复杂度ok,那么建好虚树后怎么树形dp?

设:dp[u]为u子树中所有点的欧拉函数 * u到该点的距离和,sum[u]为u子树欧拉函数的总和,d为点u的权值。

由儿子 v 到 u 的转移方程:dp[u]+=dp[v]+w*sum[v] (w为u v的距离)

很显然点 u 的贡献为:phi[d[u]]*dp[u],然后进行换根dp,转移方程很简单不多说。

#include<bits/stdc++.h>
#define pi pair<int,int>
#define mk make_pair
#define ll long long
using namespace std;
const int maxn=2e5+10,mod=1e9+7;
int dep[maxn],f[maxn],top[maxn],id[maxn],sz[maxn],son[maxn],Cnt;
int a[maxn],b[maxn],c[maxn],d[maxn],s[maxn],dp[maxn],sum[maxn],n,Top;
int cnt,pri[maxn],phi[maxn],vis[maxn],F[maxn],mu[maxn];
vector<pi>G[maxn],G2[maxn];
void init()
{
    ll res=1;
    phi[1]=mu[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(!vis[i])
            pri[++cnt]=i,phi[i]=i-1,mu[i]=-1;
        for(int j=1;j<=cnt&&pri[j]*i<=n;j++)
        {
            vis[pri[j]*i]=1;
            if(i%pri[j])
                phi[i*pri[j]]=phi[i]*phi[pri[j]],mu[i*pri[j]]=-mu[i];
            else
            {
                phi[i*pri[j]]=phi[i]*pri[j];
                break;
            }
        }
    }
}
ll ksm(ll x,int y)
{
    ll res=1;
    while(y)
    {
        if(y&1)
            res=res*x%mod;
        x=x*x%mod;
        y/=2;
    }
    return res;
}
void add(int &x,int y)
{
    x=x-mod+y;
    while(x<0)
        x+=mod;
}
void dfs1(int u,int fa,int deep)
{

    dep[u]=deep;
    f[u]=fa;
    sz[u]=1;
    for(auto tmp : G[u]){
        int v=tmp.first;
        if(v==fa)
            continue;
        dfs1(v,u,deep+1);
        sz[u]+=sz[v];
        if(sz[son[u]]<sz[v])
            son[u]=v;
    }
}
void dfs2(int u,int rt)
{
    top[u]=rt;
    id[u]=++Cnt;
    if(son[u])
        dfs2(son[u],rt);
    for(auto tmp : G[u]) {
        int v=tmp.first;
        if(v!=f[u]&&v!=son[u])
            dfs2(v,v);
    }
}
int LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])
            swap(x,y);
        x=f[top[x]];
    }
    if(dep[x]>dep[y])
        swap(x,y);
    return x;
}
bool cmp(int x,int y)
{
    return id[x]<id[y];
}
void Add(int x,int y)
{
    int z=abs(dep[x]-dep[y]);
    G2[x].push_back(mk(y,z));
    G2[y].push_back(mk(x,z));
}
void insert(int x)
{
    if(Top==1)
    {
        s[++Top]=x;
        return;
    }
    int lca=LCA(x,s[Top]);
    if(lca==s[Top])
    {
        s[++Top]=x;
        return;
    }
    while(Top>1&&id[s[Top-1]]>=id[lca])
        Add(s[Top-1],s[Top]),Top--;
    if(lca!=s[Top])
        Add(lca,s[Top]),s[Top]=lca,d[lca]=0;
    s[++Top]=x;
}
void dfs3(int u,int fa)
{
    dp[u]=0;
    sum[u]=phi[d[u]];
    for(auto tmp : G2[u]) {
        if(tmp.first==fa)
            continue;
        dfs3(tmp.first,u);
        add(sum[u],sum[tmp.first]);
        add(dp[u],(dp[tmp.first]+1ll*tmp.second*sum[tmp.first]%mod)%mod);
    }
}
void dfs4(int &ans,int u,int fa)
{
    add(ans,1ll*phi[d[u]]*dp[u]%mod);
    for(auto tmp : G2[u]) {
        int v=tmp.first,w=tmp.second;
        if(v==fa)
            continue;
        int temp=dp[u],temp2=(dp[v]+1ll*w*sum[v]%mod)%mod;
        add(temp,mod-temp2);
        add(dp[v],(temp+1ll*w*(sum[u]-sum[v]+mod)%mod)%mod);
        sum[v]=sum[u];
        dfs4(ans,v,u);
    }
    G2[u].clear();
}
int main()
{
    int u,v,ans=0;
    scanf("%d",&n);
    init();
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),b[a[i]]=i;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        G[u].push_back(mk(v,1));
        G[v].push_back(mk(u,1));
    }
    dfs1(1,0,1);
    dfs2(1,1);
    for(int i=1;i<=n;i++)
    {
        int cur=0;
        for(int j=i;j<=n;j+=i)
            c[++cur]=b[j],d[b[j]]=j;
        sort(c+1,c+1+cur,cmp);
        s[Top=1]=0;
        for(int i=1;i<=cur;i++)
            insert(c[i]);
        while(Top>1)
            Add(s[Top-1],s[Top]),Top--;
        dfs3(0,0);
        dfs4(F[i],0,0);
    }
    for(int i=1;i<=n;i++)
    {
        int res=0;
        for(int j=i;j<=n;j+=i)
            add(res,mu[j/i]*F[j]);
        add(ans,1ll*i*ksm(phi[i],mod-2)%mod*res%mod);
    }
    ans=1ll*ans*ksm(1ll*n*(n-1)%mod,mod-2)%mod;
    printf("%d\n",ans);
}

 

                 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值