【CF809E】Surprise me!(动态规划,虚树,莫比乌斯反演)

本文介绍了一道结合树形DP与莫比乌斯反演的经典算法题目,通过对题目的详细解析,展示了如何巧妙地运用数学工具简化复杂问题的方法。文章通过具体的代码实现进一步加深了读者的理解。

题面

洛谷
CodeForces
翻译:
给定一棵 n n 个节点的树,每个点有一个权值a[i],保证 a[i] a [ i ] 是一个 1..n 1.. n 的排列。

1n(n1)i=1nj=1nφ(aiaj)·dist(i,j) 1 n ( n − 1 ) ∑ i = 1 n ∑ j = 1 n φ ( a i ∗ a j ) · d i s t ( i , j )

其中, φ(x) φ ( x ) 是欧拉函数, dist(i,j) d i s t ( i , j ) 表示 i,j i , j 两个节点在树上的距离。

题解

神题啊。。。
首先来个轻松而有趣的结论
φ(ab)=φ(a)φ(b)dφ(d) φ ( a ∗ b ) = φ ( a ) ∗ φ ( b ) ∗ d φ ( d ) ,其中, d=gcd(a,b) d = g c d ( a , b )
证明?大力分解质因数就好啦

现在我们来推式子(好久没有推过式子啦)
先不管前面那个东西

Ans=i=1nj=1nφ(ai)φ(aj)gcd(ai,aj)φ(gcd(ai,aj)dist(i,j)=d=1ndφ(d)i=1nj=1n[gcd(ai,aj)=d]φ(ai)φ(aj)dist(i,j) A n s = ∑ i = 1 n ∑ j = 1 n φ ( a i ) φ ( a j ) g c d ( a i , a j ) φ ( g c d ( a i , a j ) d i s t ( i , j ) = ∑ d = 1 n d φ ( d ) ∑ i = 1 n ∑ j = 1 n [ g c d ( a i , a j ) = d ] φ ( a i ) φ ( a j ) d i s t ( i , j )

这样的东西很舒服啊

g(d)=i=1nj=1n[gcd(ai,aj)=d]φ(ai)φ(aj)dist(i,j) g ( d ) = ∑ i = 1 n ∑ j = 1 n [ g c d ( a i , a j ) = d ] φ ( a i ) φ ( a j ) d i s t ( i , j )

f(d)=i=1nj=1n[d|gcd(ai,aj)]φ(ai)φ(aj)dist(i,j) f ( d ) = ∑ i = 1 n ∑ j = 1 n [ d | g c d ( a i , a j ) ] φ ( a i ) φ ( a j ) d i s t ( i , j )

然后莫比乌斯反演就可以用啦

g(d)=d|iμ(id)f(i) g ( d ) = ∑ d | i μ ( i d ) f ( i )

现在只需要求解 f(i) f ( i )

接着推式子

Ansf(d)=d=1ndφ(d)g(d)=d=1ndφ(d)d|iμ(id)f(i)=i=1nj=1n[d|gcd(ai,aj)]φ(ai)φ(aj)dist(i,j)=i=1nj=1n[d|gcd(ai,aj)]φ(ai)φ(aj)(depi+depj2deplca) A n s = ∑ d = 1 n d φ ( d ) g ( d ) = ∑ d = 1 n d φ ( d ) ∑ d | i μ ( i d ) f ( i ) f ( d ) = ∑ i = 1 n ∑ j = 1 n [ d | g c d ( a i , a j ) ] φ ( a i ) φ ( a j ) d i s t ( i , j ) = ∑ i = 1 n ∑ j = 1 n [ d | g c d ( a i , a j ) ] φ ( a i ) φ ( a j ) ( d e p i + d e p j − 2 ∗ d e p l c a )

我们发现,对于 d|gcd(ai,aj) d | g c d ( a i , a j ) 的所有 i,j i , j

相当于满足 ai,aj a i , a j d d 的倍数

如果只是求前面的两项只与自身有关的depi+depj

这个问题就非常好解。

但是现在多了一个 deplca d e p l c a

因此我们需要做 dp d p

把所有 d|ai d | a i 的点全部拿下来建立虚树,在虚树上 dp d p 就好了。。。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 222222
#define MOD 1000000007
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int fpow(int a,int b)
{
    int s=1;
    while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    return s;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int a[MAX],b[MAX],n;
int phi[MAX],mu[MAX],pri[MAX],tot;
bool zs[MAX];
void pre(int N)
{
    zs[1]=true;phi[1]=mu[1]=1;
    for(int i=2;i<=N;++i)
    {
        if(!zs[i])pri[++tot]=i,mu[i]=-1,phi[i]=i-1;
        for(int j=1;j<=tot&&i*pri[j]<=N;++j)
        {
            zs[i*pri[j]]=true;
            if(i%pri[j])phi[i*pri[j]]=phi[i]*phi[pri[j]],mu[i*pri[j]]=-mu[i];
            else{mu[i*pri[j]]=0,phi[i*pri[j]]=phi[i]*pri[j];break;}
        }
    }
}
int dfn[MAX],low[MAX],size[MAX],hson[MAX],top[MAX],fa[MAX],tim,dep[MAX];
void dfs1(int u,int ff)
{
    fa[u]=ff;dep[u]=dep[ff]+1;size[u]=1;
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].v;if(v==ff)continue;
        dfs1(v,u);size[u]+=size[v];
        if(size[v]>size[hson[u]])hson[u]=v;
    }
}
void dfs2(int u,int tp)
{
    top[u]=tp;dfn[u]=++tim;
    if(hson[u])dfs2(hson[u],tp);
    for(int i=h[u];i;i=e[i].next)
        if(e[i].v!=fa[u]&&e[i].v!=hson[u])
            dfs2(e[i].v,e[i].v);
    low[u]=tim;
}
int LCA(int u,int v)
{
    while(top[u]^top[v])dep[top[u]]<dep[top[v]]?v=fa[top[v]]:u=fa[top[u]];
    return dep[u]<dep[v]?u:v;
}
bool cmp(int a,int b){return dfn[a]<dfn[b];}
int ans=0,K,p[MAX<<1],S[MAX],f[MAX];
bool vis[MAX];
void Plus(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
void DP(int u)
{
    f[u]=vis[u]?phi[a[u]]:0;
    Plus(ans,MOD-1ll*f[u]*f[u]%MOD*dep[u]%MOD);
    for(int i=h[u];i;i=e[i].next)
    {
        DP(e[i].v);
        Plus(ans,MOD-2ll*f[u]*f[e[i].v]%MOD*dep[u]%MOD);
        Plus(f[u],f[e[i].v]);
    }
}
int F[MAX],G[MAX];
int main()
{
    n=read();pre(n);
    for(int i=1;i<=n;++i)a[i]=read(),b[a[i]]=i;
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        Add(u,v);Add(v,u);
    }
    dfs1(1,0);dfs2(1,1);
    memset(h,0,sizeof(h));
    for(int T=1;T<=n;++T)
    {
        K=n/T;int s1=0,s2=0;cnt=1;
        for(int i=1;i<=K;++i)p[i]=b[i*T];
        for(int i=1;i<=K;++i)
            Plus(s1,phi[a[p[i]]]),Plus(s2,1ll*dep[p[i]]*phi[a[p[i]]]%MOD);
        for(int i=1;i<=K;++i)vis[p[i]]=true;
        sort(&p[1],&p[K+1],cmp);
        for(int i=K;i>1;--i)p[++K]=LCA(p[i],p[i-1]);
        sort(&p[1],&p[K+1],cmp);K=unique(&p[1],&p[K+1])-p-1;
        for(int i=1,tp=0;i<=K;++i)
        {
            while(tp&&low[S[tp]]<dfn[p[i]])--tp;
            Add(S[tp],p[i]);S[++tp]=p[i];
        }
        DP(p[1]);Plus(ans,ans);
        Plus(ans,2ll*s1*s2%MOD);
        for(int i=1;i<=K;++i)h[p[i]]=0,vis[p[i]]=false;
        F[T]=ans;ans=0;
    }
    for(int i=1;i<=n;++i)
        for(int j=i;j<=n;j+=i)
            if(mu[j/i]!=0)Plus(G[i],(mu[j/i]==1)?F[j]:(MOD-F[j]));
    for(int i=1;i<=n;++i)G[i]=1ll*G[i]*i%MOD*fpow(phi[i],MOD-2)%MOD;
    for(int i=1;i<=n;++i)Plus(ans,G[i]);
    ans=1ll*ans*fpow(n,MOD-2)%MOD*fpow(n-1,MOD-2)%MOD;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值