【CF809E】Surprise me! 树形DP 虚树 数学

题目大意

  给你一棵\(n\)个点的树,每个点有权值\(a_i\),\(a\)为一个排列,求

\[
\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n \varphi(a_ia_j)dist_{i,j}
\]

  \(n\leq 200000\)

题解

  欧拉phi函数

\[
\begin{align}
ans&=\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n \varphi(a_ia_j)dist_{i,j}\&=\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n\sum_{d=(a_i,a_j)} \frac{\varphi(a_i)\varphi(a_j)d}{\varphi(d)}dist_{i,j}\&=\frac{1}{n(n-1)}\sum_{d=1}^n\frac{d}{\mu(d)}\sum_{d=(a_i,a_j)}\varphi(a_i)\varphi(a_j)dist_{i,j}\f(d)&=\sum_{d=(a_i,a_j)}\varphi(a_i)\varphi(a_j)dist_{i,j}\F(d)&=\sum_{d|a_i,d|a_j}\varphi(a_i)\varphi(a_j)dist_{i,j}\F(d)&=\sum_{d|n}f(n)\f(d)&=F(d)-\sum_{d|n,d\neq n}f(n)
\end{align}
\]

  \(F(d)\)可以直接建虚树DP求。

  然后直接反演统计就可以得到答案。

  总的点数是\(\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor=O(n\log n)\)

  所以总的时间复杂度是\(O(n\log^2 n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
ll p=1000000007;
struct graph
{
    int h[200010];
    int v[400010];
    int w[400010];
    int t[400010];
    int n;
    graph()
    {
        memset(h,0,sizeof h);
        n=0;
    }
    void add(int x,int y,int z)
    {
        n++;
        v[n]=y;
        w[n]=z;
        t[n]=h[x];
        h[x]=n;
    }
};
graph g,g2;
int f[200010][20];
int d[200010];
int st[200010];
int ti;
void dfs(int x,int fa,int dep)
{
    f[x][0]=fa;
    d[x]=dep;
    st[x]=++ti;
    int i;
    for(i=1;i<=19;i++)
        f[x][i]=f[f[x][i-1]][i-1];
    for(i=g.h[x];i;i=g.t[i])
        if(g.v[i]!=fa)
            dfs(g.v[i],x,dep+1);
}
int getlca(int x,int y)
{
    if(d[x]<d[y])
        swap(x,y);
    int i;
    for(i=19;i>=0;i--)
        if(d[f[x][i]]>=d[y])
            x=f[x][i];
    if(x==y)
        return x;
    for(i=19;i>=0;i--)
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}
ll phi[200010];
int b[200010];
int pri[100010];
int cnt;
ll inv[200010];
void init(int n)
{
    int i,j;
    inv[0]=inv[1]=1;
    for(i=2;i<=n;i++)
        inv[i]=-(p/i)*inv[p%i]%p;
    phi[1]=1;
    cnt=0;
    for(i=2;i<=n;i++)
    {
        if(!b[i])
        {
            pri[++cnt]=i;
            phi[i]=i-1;
        }
        for(j=1;j<=cnt&&i*pri[j]<=n;j++)
        {
            b[i*pri[j]]=1;
            if(i%pri[j]==0)
            {
                phi[i*pri[j]]=phi[i]*pri[j];
                break;
            }
            phi[i*pri[j]]=phi[i]*phi[pri[j]];
        }
    }
}
ll a[200010];
ll s[200010];
int c[200010];
int c1[200010];
int ct;
int n;
int stack[200010];
int top;
int cmp(int a,int b)
{
    return st[a]<st[b];
}
ll s1[200010];
ll s2[200010];
ll sum;
void add(int x,int y)//f[x]=y
{
    ll s3=(s1[x]+(d[x]-d[y])*s2[x])%p;
    sum=(sum+s3*s2[y]+s1[y]*s2[x])%p;
    s1[y]=(s1[y]+s3)%p;
    s2[y]=(s2[y]+s2[x])%p;
}
ll solve(int x)
{
    sum=0;
    ct=top=0;
    int i;
    for(i=x;i<=n;i+=x)
        c1[++ct]=c[i];
    sort(c1+1,c1+ct+1,cmp);
    int rt=getlca(c1[1],c1[ct]);
    if(rt!=c1[1])
    {
        stack[++top]=rt;
        s1[rt]=s2[rt]=0;
    }
    for(i=1;i<=ct;i++)
    {
         if(i>=2)
         {
            int lca=getlca(c1[i],c1[i-1]);
            while(d[stack[top]]>d[lca])
                if(d[stack[top-1]]<d[lca])
                {
                    s1[lca]=s2[lca]=0;
                    add(stack[top],lca);
                    stack[top]=lca;
                }
                else
                {
                    add(stack[top],stack[top-1]);
                    top--;
                }
         }
         stack[++top]=c1[i];
         s1[c1[i]]=0;
         s2[c1[i]]=phi[a[c1[i]]];
    }
    while(top>1)
    {
        add(stack[top],stack[top-1]);
        top--;
    }
    return sum*2%p;
}
int main()
{
    scanf("%d",&n);
    init(n);
    int i,x,y,j;
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        c[a[i]]=i;
    }
    for(i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        g.add(x,y,0);
        g.add(y,x,0);
    }
    dfs(1,0,1);
    for(i=1;i<=n;i++)
        s[i]=solve(i);
    ll ans=0;
    for(i=n;i>=1;i--)
    {
        for(j=i+i;j<=n;j+=i)
            s[i]-=s[j];
        ans=(ans+s[i]*i%p*inv[phi[i]]%p)%p;
    }
    ans=ans*inv[n]%p*inv[n-1]%p;
    ans=(ans+p)%p;
    printf("%lld\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/ywwyww/p/8511453.html

时间: 2024-10-16 15:57:37

【CF809E】Surprise me! 树形DP 虚树 数学的相关文章

3611: [Heoi2014]大project|树形DP|虚树

构建出虚树然后DP统计答案 自己写的DP太傻QAQ,各种WA 膜了一发PoPoQQQ大爷的DP方法 mxdis,mndis分别表示到当前点近期和最远的被选出来的点的距离 mx,mn分别表示在以当前点为根的情况下距离最远的两点的距离和距离近期的两点的距离. sum表示在以当前点为根的子树中,全部关键的到当前点的距离之和 c数组表示以当前点为根的子树中.关键点的个数 然后我用了个时间戳来标记关键点 #include<algorithm> #include<iostream> #incl

树形DP求树的重心 --SGU 134

令一个点的属性值为:去除这个点以及与这个点相连的所有边后得到的连通分量的节点数的最大值. 则树的重心定义为:一个点,这个点的属性值在所有点中是最小的. SGU 134 即要找出所有的重心,并且找出重心的属性值. 考虑用树形DP. dp[u]表示割去u点,得到的连通分支的节点数的最大值. tot[u]记录以u为根的这棵子树的节点数总和(包括根). 则用一次dfs即可预处理出这两个数组.再枚举每个点,每个点的属性值其实为max(dp[u],n-tot[u]),因为有可能最大的连通分支在u的父亲及以上

树形dp求树的重心

Balancing Act http://poj.org/problem?id=1655 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #define mt(a,b) memset(a,b,sizeof(a)) 6 using namespace std; 7 const int M=50010; 8 vector<int> g[

BZOJ 2286: [Sdoi2011消耗战 [DP 虚树]

传送门 题意: 删除价值和最小的边使得$1$号点与$k$个关键点不连通 一个树形DP...但是询问多次,保证总的关键点数为$O(n)$ 先说一下这个$DP$ $f[i]$表示子树$i$中的关键点与$1$不连通的最小价值 如果$i$是关键点则必须删除$i$到$1$的权值最小的边,否则$\sum f[child\ of\ i]$ 学了一下虚树...找不到别的资料啊只有别人的$Blog$ 试验了好多写法 貌似其中有好多带$Bug$的写法 最终定下了现在的版本应该是没大有问题的吧...明天再做两道虚树,

hdu5293 Tree chain problem 树形dp+线段树

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5293 在一棵树中,给出若干条链和链的权值.求选取不相交的链使得权值和最大. 比赛的时候以为是树链剖分就果断没去想,事实上是没思路. 看了题解,原来是树形dp.话说多校第一场树形dp还真多. . .. 维护d[i],表示以i为根节点的子树的最优答案. sum[i]表示i的儿子节点(仅仅能是儿子节点)的d值和. 那么答案就是d[root]. 怎样更新d值 d[i] = max(sum[i] , w[p]+s

poj 1655 and 3107 and 2378 树形dp(树的重心问题)

简单的树形dp,顺便学习了树的重心的概念,即以该点为根的树的最大子树的结点数最少. poj 1655: 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 20001; 7 int head[N]; 8 int balance[N]; 9 int child[N]; 10 int n, e; 11 12 struct

【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树形dp/LCA/树的点分治

题目描述 农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样, 图中农场用F1..F7表示, 每个农场最多能在东西南北四个方向连结4个不同的农场.此外,农场只处在道路的两端.道路不会交叉且每对农场间有且仅有一条路径.邻居鲍伯要约翰来导航,但约翰丢了农场的地图,他只得从电脑的备份中修复了.每一条道路的信息如下: 从农场23往南经距离10到达农场17 从农场1往东经距离7到达农

POJ 3162 Walking Race 树形DP+线段树

给出一棵树,编号为1~n,给出数m 漂亮mm连续n天锻炼身体,每天会以节点i为起点,走到离i最远距离的节点 走了n天之后,mm想到知道自己这n天的锻炼效果 于是mm把这n天每一天走的距离记录在一起,成为一段长度为n的数列 现在mm想要从这数列中选出一个连续的区间,要求这个区间的max-min<=m 输出最长的区间 做了一个下午 思路: 分成2个部分: 1.求出数列,即对于一棵树,求出每一个节点能到达的最远距离 2.对于这段数列,选出一个区间,使得区间的max-min<=m,并且使得区间长度尽量

Codeforces 671D. Roads in Yusland(树形DP+线段树)

调了半天居然还能是线段树写错了,药丸 这题大概是类似一个树形DP的东西.设$dp[i]$为修完i这棵子树的最小代价,假设当前点为$x$,但是转移的时候我们不知道子节点到底有没有一条越过$x$的路.如果我们枚举每条路去转移,会发现这条路沿线上的其他子树的答案难以统计,那怎么办呢,我们可以让这条路向上回溯的时候顺便记录一下,于是有$val[i]$表示必修i这条路,并且修完当前子树的最小代价. 则有转移$dp[x]=min(val[j])$,且$j$这条路必须覆盖$x$. $val[i]=(\sum