【题解】「WC2019」数树

NOI十合一

请先保证有足够的耐心看完本题解

首先,若两树的公共边有\(k\)条,则给予数方案总共有\(y^{n-k}\)种。我有一个很好的证明,可惜这里太小写不下。

这样就能轻松通过subtask1了。

显然,subtask1在引导我们向公共边的数量取思考。

\(f(i)\)表示两棵树\(i\)条公共边的的方案数(注意,不是给予数方案数)。则
\[ \large \begin{align} ans&=\sum_{i=0}^{n-1}y^{n-i}f(i)\\ &=y^n\sum_{i=0}^{n-1}y^{-i}f(i)\\ \end{align} \]
然而,直接求\(f(i)\)并不好求,于是可以设\(g(i)\)表示两棵树有\(i\)确定的公共边的的方案数。可以得到,
\[ \large g(i)=\sum_{j=i}^{n-1}C_j^if(j) \]
可以由每一个恰有\(j\)条边的方案中选择\(i\)条边推得。

观察到\(f(j)\)乘的组合数都是\(C_j^*\)类型的,于是就有一个神奇的变换:
\[ \large \begin{align} ans&=y^n\sum_{i=0}^{n-1}(\frac 1y)^if(i)\\ &=y^n\sum_{i=0}^{n-1}\sum_{j=0}^if(i)C_i^j(\frac 1y-1)^j\\ &=y^n\sum_{j=0}^{n-1}(\frac 1y-1)^j\sum_{i=j}^{n-1}C_i^jf(i)\\ &=y^n\sum_{i=0}^{n-1}(\frac 1y-1)^ig(i) \end{align} \]
那么,\(g(i)\)该怎么求呢?

对于subtask2,设选出来的\(i\)条公共边构成的\(n-i\)个连通块大小分别为\(a_1,a_2,\cdots,a_{n-i}\),则有:
\[ \large g(i)=\sum_an^{n-i-2}\prod_{j=1}^{n-i}a_j \]
证明:将每个连通块缩点,再进行生成树。设树边由儿子连向父亲,则prufer序的每一位都可以选择任意一块,每一块都可以选择任意一个点,这样入边总共产生了\(n^{n-i-2}\)的贡献。再乘上出边的\(\prod a_j\)的贡献。

所以,设\(p=\frac 1y-1\),有:
\[ \large \begin{align} ans&=y^n\sum_{i=0}^{n-1}p^in^{n-i-2}\sum_a\prod_{j=1}^{n-i}a_j\\ &=y^np^nn^{-2}\sum_a\prod_{j=1}^{|a|}a_j\times \frac np \end{align} \]
\(v=\frac np\),根据上面的方程,很容易列出一个简单的\(dp\),设\(dp_{i,j}\)表示,当前讨论了以\(i\)号点为根的子树,该子树内还有\(j\)个点没被划进公共边连通块内。显然转移是一个卷积式,可以设\(h_i(x)\)\(i\)号点的生成函数。列出方程:
\[ \large \begin{align} &h_i(x)=\sum_{j=0}^\infty dp_{i,j}x^j\\ &h_i(x)=dp_{i,0}+x\prod_{j\in son_i}h_j(x)\\ &dp_{i,0}=v\sum_{j=1}^\infty dp_{i,j}\times j=v\times h_i'(1)\\ \therefore&\ h_i(x)=v\times h_i'(1)+x\prod_{j\in son_i}h_j(x) \end{align} \]

容易发现,\(ans=y^np^nn^{-2}dp_{1,0}=y^np^nn^{-2}v\times h_1'(1)\),所以,计算时只用到了\(h_i(1)\)\(h_i'(1)\)。直接套用式子
\[ \large \begin{align} h_i'(1)&=(1+\sum_{j\in son_i}\frac{h_j'(1)}{h_j(1)})\prod_{j\in son_i}h_j(1)\\ h_i&=h_i'(1)+\prod_{j\in son_i}h_j(1) \end{align} \]
\(O(n\log998244353)\)搞定。

接着看subtask3,求\(g(i)\)的方法类似上一个子任务。不过结果有点不同:
\[ \large g(i)=\sum_{a}n^{2(n-i-2)}\prod_{j=1}^{n-i}a_j^{a_j}=n^{-4}\sum_a\prod_{j=1}^{n-i}a_j^{a_j}n^2 \]
很简单,就是将之前的\(g(i)\)平方,然后对每一个连通块再乘一个生成树数量\(a_j^{a_j-2}\)

但是在数连通块的时候很容易数重,可以把所有连通块先按照大小排序,再按照最小节点编号排序。设大小为\(a\)的连通块有\(t_a\)个,则
\[ \large \begin{align} g(i)&=n^{-4}n!\sum_t\prod_{a=1}^n\frac{(\frac{a^an^2}{a!})^{t_a}}{t_a!}\\ ans&=y^n\sum_{i=0}^{n=1}p^ig(i)\\ &=y^np^nn^{-4}n!\sum_t\prod_{a=1}^n\frac{(\frac{a^an^2}{a!p})^{t_a}}{t_a!} \end{align} \]
\(v_a=\frac{a^an^2}{a!p}\),则
\[ \large ans=y^np^nn^{-4}n!\sum_t\prod_{a=1}^n\frac{v_a^{t_a}}{t_a!} \]
又由于\(\sum t_aa=n\),设
\[ \large h_a(x)=\sum_{t=0}^\infty \frac{v_a^tx^{at}}{t!}=e^{v_ax^a} \]

\[ \large \begin{align} ans&=y^np^nn^{-4}n!(\prod_{a=1}^{n}h_a(x))[x^n]\\ &=y^np^nn^{-4}n!(\exp(\sum v_ax^a))[x^n] \end{align} \]
先处理出\(v\),再用多项式\(\exp\)就可以解决。复杂度\(O(n\log n)\)

code(去掉了namespace poly):

#include<stdio.h>
#include<vector>
#include<algorithm>
#define inf 998244353
int n,y,hdhdAKIOI;
namespace sub0{
    std::pair<int,int>a[200002];
    void solve(){
        for(int i=1;i<=n+n-2;i++){
            int p,q;scanf("%d%d",&p,&q);
            if(p>q)p^=q^=p^=q;a[i]=std::make_pair(p,q);
        }std::sort(a+1,a+n+n-1);
        int cnt=n;
        for(int i=2;i<=n+n-1;i++)cnt-=a[i]==a[i-1];
        printf("%d",poly::ksm(y,cnt));
    }
}
namespace sub1{
    int f[100002][2],v;
    int Last[100002],Next[200002],End[200002];
    void dfs(int p,int F){
        register unsigned long long s=1,cnt=1;
        for(int i=Last[p];i;i=Next[i])if(End[i]!=F){
            dfs(End[i],p);
            s=s*f[End[i]][0]%inf;
            cnt+=1ull*f[End[i]][1]*poly::getinv(f[End[i]][0]);
            if(cnt>=1ull*inf*inf)cnt-=1ull*inf*inf;
        }cnt%=inf;
        f[p][1]=s*cnt%inf;
        f[p][0]=(s+1ull*v*f[p][1])%inf;
    }
    void solve(){
        if(y==1)return void(printf("%d",poly::ksm(n,n-2)));
        for(int i=1;i<n+n-2;i+=2){
            scanf("%d%d",&End[i+1],&End[i]);
            Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
            Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
        }int p=poly::getinv(y)-1;
        v=1ull*poly::getinv(p)*n%inf;
        dfs(1,0);
        printf("%lld\n",1ull*f[1][1]*v%inf*poly::ksm(1ull*y*p%inf,n)%inf*poly::ksm(n,inf-3)%inf);
    }
}
namespace sub2{
    int v[524288],tmp1[524288],tmp2[524288],a[524288],fac[524288],ifac[524288];
    void solve(){
        if(y==1)return void(printf("%d",poly::ksm(n,n*2-4)));
        fac[0]=1;
        for(int i=1;i<=n;i++)
            fac[i]=1ull*i*fac[i-1]%inf;
        ifac[n]=poly::getinv(fac[n]);
        for(int i=n-1;i>=0;i--)
            ifac[i]=1ull*(i+1)*ifac[i+1]%inf;
        int p=poly::getinv(y)-1,invp=poly::getinv(p);
        for(int i=1;i<=n;i++)
            v[i]=1ull*poly::ksm(i,i+inf-1)*ifac[i]%inf*n%inf*n%inf*invp%inf;
        int len=poly::gett(n);
        poly::getexp(v,a,tmp1,tmp2,len);
        printf("%lld",1ull*a[n]*poly::ksm(1ull*y*p%inf,n)%inf*poly::ksm(n,inf-5)%inf*fac[n]%inf);
    }
}
int main(){
    poly::init();
    scanf("%d%d%d",&n,&y,&hdhdAKIOI);
    if(hdhdAKIOI==0)sub0::solve();
    else if(hdhdAKIOI==1)sub1::solve();
    else if(hdhdAKIOI==2)sub2::solve();
}

猜你喜欢

转载自www.cnblogs.com/ztc03/p/12364494.html