Tree (树形+换根dp)

链接:https://ac.nowcoder.com/acm/problem/19782
来源:牛客网

修修去年种下了一棵树,现在它已经有n个结点了。
修修非常擅长数数,他很快就数出了包含每个点的连通点集的数量。
澜澜也想知道答案,但他不会数数,于是他把问题交给了你。

题目描述:问对每个点来说,以该点为根,向外延伸,能形成多少棵子树。自己一个点也算一棵子树。

思路:

首先考虑树形dp求一个根的答案。dp1[u]记为以u为根,和他的儿子以及后代能形成的所有子树数。u的所有儿子v的dp1[v]+1的乘积。

void dfs1(int x,int fa)
{
    //先求一个根的答案
    for(int son:to[x])
    {
        if(son==fa) continue;
        dfs1(son,x);
        dp1[x]=(dp1[x]*(dp1[son]+1))%mod;
    }
}

考虑以一个知道的根节点答案换根dp求其他答案。dp2[u]记为以u为根,和整棵树其他点能形成的所有子树数量。(即最终答案)

当从fa的答案换根到他的儿子son时,通过找规律或某些想法可知:dp2[son]=(dp2[fa] / (dp1[son]+1)+1) *dp1[son]。

 

这有一个很坑的地方,就是(dp1[son]+1)求逆元可能得到0(dp1[son]可能为mod-1),这时相当于除于0,就会出问题。

当逆元inv为0时,看到dp2[fa]实际是由在树形dp的时候求出的dp1[fa],而dp1[fa]又等于(他所有儿子dp1的值+1)的乘积。

所以dp2[fa] / (dp1[son]+1)又可以变成fa其他儿子的乘积:fa除son外的其他儿子记brother。

(dp1[brother_1]+1) * (dp1[brother_2]+1) * .....他的所有兄弟的值乘积。

void dfs2(int x,int fa)
{
    //换根算答案
    for(int son:to[x])
    {
        if(son==fa) continue;
        //求儿子的dp2值,放dfs上面
        ll inv=mp(dp1[son]+1,mod-2);
        if(inv!=0)
        {
            dp2[son]=(dp2[x]*inv%mod+1)%mod*dp1[son]%mod;
        }
        else
        {
            //逆元为0,不能直接乘,暴力算贡献
            ll tmp=1;
            for(int brother:to[x])
            {
                if(brother==son||brother==fa) continue;
                tmp=tmp*(dp1[brother]+1)%mod;
            }
            dp2[son]=(tmp+1)*dp1[son]%mod;
        }
        dfs2(son,x);   
    }
}

 

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<sstream>
#include<vector>
#include<stack>
#include<deque>
#include<cmath>
#include<map>
#include<queue>
#include<bitset>
//#include<hash_map>
#define sd(x) scanf("%d",&x)
#define lsd(x) scanf("%lld",&x)
#define ms(x,y) memset(x,y,sizeof x)
#define fu(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define all(a) a.begin(),a.end()
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
//using namespace __gnu_cxx;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int maxn=1e6+79;
const int mod=1e9+7;
const int INF=1e9+7;
const double pi=acos(-1);
ll mp(ll x,ll n){ll res=1; while(n){if(n&1) res=res*x%mod;x=x*x%mod;n>>=1;} return res;}

ll dp1[maxn],dp2[maxn];
//树形dp+换根dp
vector<int> to[maxn];
void dfs1(int x,int fa)
{
    //先求一个根的答案
    for(int son:to[x])
    {
        if(son==fa) continue;
        dfs1(son,x);
        dp1[x]=(dp1[x]*(dp1[son]+1))%mod;
    }
}
void dfs2(int x,int fa)
{
    //换根算答案
    for(int son:to[x])
    {
        if(son==fa) continue;
        //求儿子的dp2值,放dfs上面
        ll inv=mp(dp1[son]+1,mod-2);
        if(inv!=0)
        {
            dp2[son]=(dp2[x]*inv%mod+1)%mod*dp1[son]%mod;
        }
        else
        {
            //逆元为0,不能直接乘,暴力算贡献
            ll tmp=1;
            for(int brother:to[x])
            {
                if(brother==son||brother==fa) continue;
                tmp=tmp*(dp1[brother]+1)%mod;
            }
            dp2[son]=(tmp+1)*dp1[son]%mod;
        }
        dfs2(son,x);   
    }
}
int main()
{
    int n;sd(n);
    fu(i,1,n-1)
    {
        int u,v;sd(u);sd(v);
        to[u].push_back(v);
        to[v].push_back(u);
    }
    fu(i,1,n) 
    {
        //自己一个点可以算一棵子树
        dp1[i]=1;
    }
    dfs1(1,0);
    dp2[1]=dp1[1];//从1开始换根
    dfs2(1,0);
    fu(i,1,n) printf("%lld\n",dp2[i]);
    return 0;
}

 

posted on 2020-08-17 21:13  Aminers  阅读(305)  评论(0编辑  收藏  举报

导航