【WC2019】数树 树形DP 多项式exp

题目大意

  有两棵 \(n\) 个点的树 \(T_1\) 和 \(T_2\)。

  你要给每个点一个权值吗,要求每个点的权值为 \([1,y]\) 内的整数。

  对于一条同时出现在两棵树上的边,这条边的两个端点的值相同。

  若 \(op=0\),则给你两棵树 \(T_1,T_2\),求方案数。

  若 \(op=1\),则给你一棵树 \(T_1\),求对于所有 \(n^{n-2}\) 种 \(T_2\),方案数之和。

  若 \(op=2\),则求对于所有的 \(T_1,T_2\),求方案数之和。

  \(n\leq 100000\)

题解

  新建一个图 \(G\),把两棵树的公共边加到 \(G\) 中。记 \(m\) 为图 \(G\) 的连通块数量。那么答案就是 \(y^{n-m}\)。

  令 \(z=y^{-1}\),那么答案就变成了 \(y^nz^m\)。也就是说,每有一条相同的边,方案的贡献就要 \(\times z\)。

op=0

  这个大家都会。

op=1

\[
z^m=\sum_{i=0}^m\binom{m}{i}(z-1)^i
\]

  那么可以枚举一个边集 \(E\),计算有多少种生成树包含 \(E\),然后把答案加上方案数 \(\times{(z-1)}^{\lvert E\rvert}\)。

  记这 \(E\) 条边形成了 \(m\) 个连通块,这些连通块的大小为 \(a_1,a_2,\ldots,a_m\),那么贡献就是
\[
\begin{align}
&{(z-1)}^{n-m}\sum_{\sum_{i=1}^md_i=2m-2}(m-2)!\prod_{i=1}^m\frac{a_i^{d_i}}{(d_i-1)!}\=&{(z-1)}^{n-m}n^{m-2}\prod_{i=1}^ma_i\\end{align}
\]
  \(\prod_{i=1}^ma_i\) 可以看成是每个连通块内选一个点的方案数。这样就可以DP了。

  时间复杂度:\(O(n)\)

op=2

  枚举两棵树的公共边个数:
\[
\begin{align}
s_n&=\sum_{i=1}^{n}{(z-1)}^{n-i}\sum_{\sum_{j=1}^ia_j=n}\frac{n!}{i!}(\prod_{j=1}^i\frac{a_j^{a_j-2}}{a_j!})(n^{i-2}\prod_{j=1}^ia_j)^2\&=\sum_{i=1}^{n}{(z-1)}^{n-i}\frac{n!n^{2i-4}}{i!}\sum_{\sum_{j=1}^ia_j=n}\prod_{j=1}^i\frac{a_j^{a_j}}{a_j!}\&=\sum_{i=1}^{n}{(z-1)}^{n-i}n^{2i-4}\sum_{\sum_{j=1}^ia_j=n}\prod_{j=1}^i\binom{(\sum_{k=1}^ja_k)-1}{a_j-1}{}a_j^{a_j}\\end{align}
\]
  记 \(f_l=\sum_{i=1}^{l}{(z-1)}^{-i}n^{2i}\sum_{\sum_{j=1}^ia_j=l}\prod_{j=1}^i\binom{(\sum_{k=1}^ja_k)-1}{a_j-1}{}a_j^{a_j}\)。

  转移时枚举最后一块的大小,有:
\[
f_i=\begin{cases}
1&,i=0\\sum_{j=1}^i\frac{(i-1)!n^2j^jf_{i-j}}{(i-j)!(j-1)!(z-1)}&,i>0
\end{cases}
\]
  直接DP是 \(O(n^2)\) 的。

  记 \(g_i=\sum_{i\geq 1}\frac{n^2i^i}{(i-1)!(z-1)}\),\(F(x)\) 为 \(f\) 的 EGF,\(G(x)\) 为 \(g\) 的 OGF,那么
\[
\begin{align}
xF'(x)&=F(x)G(x)\\frac{F'(x)}{F(x)}&=\frac{G(x)}{x}\\ln F(x)&=\int \frac{G(x)}{x}\F(x)&=e^{\int \frac{G(x)}{x}}
\end{align}
\]
  直接多项式 exp 就好了。

  答案为 \((z-1)^nn^{-4}f_n\)

  时间复杂度:\(O(n\log n)\)

代码

const ll p=998244353;
ll fp(ll a,ll b)
{
    ll s=1;
    for(;b;b>>=1,a=a*a%p)
        if(b&1)
            s=s*a%p;
    return s;
}
const int N=100010;
int n,op;
ll z,_z;
ll ans;
namespace solve0
{
    map<int,int> a[N];
    void solve()
    {
        if(_z==1)
        {
            ans=1;
            return;
        }
        int x,y;
        for(int i=1;i<n;i++)
        {
            io::get(x);
            io::get(y);
            if(x>y)
                swap(x,y);
            a[x][y]++;
        }
        ans=1;
        for(int i=1;i<n;i++)
        {
            io::get(x);
            io::get(y);
            if(x>y)
                swap(x,y);
            if(a[x].count(y))
                ans=ans*z%p;
        }
    }
}
namespace solve1
{
    vector<int> g[N];
    ll f[N][2];
    void dfs(int x,int fa)
    {
        f[x][0]=f[x][1]=1;
        for(auto v:g[x])
            if(v!=fa)
            {
                dfs(v,x);
                ll s0=(f[x][0]*f[v][0]%p*z+f[x][0]*f[v][1]%p*n)%p;
                ll s1=(f[x][0]*f[v][1]%p*z+f[x][1]*f[v][0]%p*z+f[x][1]*f[v][1]%p*n)%p;
                f[x][0]=s0;
                f[x][1]=s1;
            }
    }
    void solve()
    {
        if(_z==1)
        {
            ans=fp(n,n-2);
            return;
        }
        int x,y;
        for(int i=1;i<n;i++)
        {
            io::get(x);
            io::get(y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        z--;
        dfs(1,0);
        ans=f[1][1]*fp(n,p-2)%p;
    }
}
namespace solve2
{
    const int N=270000;
    namespace ntt
    {
        const int W=262144;
        ll w[N];
        int rev[N];
        void init()
        {
            w[0]=1;
            ll s=fp(3,(p-1)/W);
            for(int i=1;i<W/2;i++)
                w[i]=w[i-1]*s%p;
        }
        void ntt(ll *a,int n,int t)
        {
            for(int i=1;i<n;i++)
            {
                rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
                if(rev[i]>i)
                    swap(a[i],a[rev[i]]);
            }
            for(int i=2;i<=n;i<<=1)
                for(int j=0;j<n;j+=i)
                    for(int k=0;k<i/2;k++)
                    {
                        ll u=a[j+k];
                        ll v=a[j+k+i/2]*w[W/i*k];
                        a[j+k]=(u+v)%p;
                        a[j+k+i/2]=(u-v)%p;
                    }
            if(t==-1)
            {
                reverse(a+1,a+n);
                ll inv=fp(n,p-2);
                for(int i=0;i<n;i++)
                    a[i]=a[i]*inv%p;
            }
        }
        void mul(ll *a,ll *b,ll *c,int n,int m,int l)
        {
            static ll a1[N],a2[N];
            int k=1;
            while(k<=n+m)
                k<<=1;
            memset(a1,0,sizeof(a1[0])*k);
            memset(a2,0,sizeof(a2[0])*k);
            memcpy(a1,a,sizeof(a1[0])*(n+1));
            memcpy(a2,b,sizeof(a2[0])*(m+1));
            ntt::ntt(a1,k,1);
            ntt::ntt(a2,k,1);
            for(int i=0;i<k;i++)
                a1[i]=a1[i]*a2[i]%p;
            ntt::ntt(a1,k,-1);
            memcpy(c,a1,sizeof(a1[0])*(l+1));
        }
        void inv(ll *a,ll *b,int n)
        {
            if(n==1)
            {
                b[0]=fp(a[0],p-2);
                return;
            }
            inv(a,b,n>>1);
            static ll a1[N],a2[N];
            memset(a1,0,sizeof(a1[0])*(n<<1));
            memset(a2,0,sizeof(a2[0])*(n<<1));
            memcpy(a1,a,sizeof(a1[0])*n);
            memcpy(a2,b,sizeof(a2[0])*(n>>1));
            ntt(a1,n<<1,1);
            ntt(a2,n<<1,1);
            for(int i=0;i<n<<1;i++)
                a1[i]=a2[i]*(2-a1[i]*a2[i]%p)%p;
            ntt(a1,n<<1,-1);
            memcpy(b,a1,sizeof(a1[0])*n);
        }
        void ln(ll *a,ll *b,int n)
        {
            static ll a1[N],a2[N],a3[N];
            for(int i=1;i<n;i++)
                a1[i-1]=a[i]*i%p;
            a1[n-1]=0;
            inv(a,a2,n);
            mul(a1,a2,a3,n-1,n-1,n-1);
            for(int i=1;i<n;i++)
                b[i]=a3[i-1]*fp(i,p-2)%p;
            b[0]=0;
        }
        void exp(ll *a,ll *b,int n)
        {
            if(n==1)
            {
                b[0]=1;
                return;
            }
            exp(a,b,n>>1);
            static ll a1[N],a2[N],a3[N];
            memset(b+(n>>1),0,sizeof(b[0])*(n>>1));
            ln(b,a3,n);
            memset(a1,0,sizeof(a1[0])*n);
            memset(a2,0,sizeof(a2[0])*n);
            memcpy(a1,b,sizeof(a1[0])*(n>>1));
            for(int i=0;i<(n>>1);i++)
                a2[i]=a[(n>>1)+i]-a3[(n>>1)+i];
            ntt(a1,n,1);
            ntt(a2,n,1);
            for(int i=0;i<n;i++)
                a1[i]=a1[i]*a2[i]%p;
            ntt(a1,n,-1);
            memcpy(b+(n>>1),a1,sizeof(a1[0])*(n>>1));
        }
    }
    ll inv[N],fac[N],ifac[N];
    ll f[N],g[N],w[N];
    void solve()
    {
        if(_z==1)
        {
            ans=fp(n,n-2)*fp(n,n-2)%p;
            return;
        }
        z--;
        ntt::init();
        fac[0]=fac[1]=ifac[0]=ifac[1]=inv[1]=1;
        for(int i=2;i<=n;i++)
        {
            fac[i]=fac[i-1]*i%p;
            inv[i]=-p/i*inv[p%i]%p;
            ifac[i]=ifac[i-1]*inv[i]%p;
        }
        ll ifacz=fp(z,p-2);

//      f[0]=1;
//      for(int i=1;i<=n;i++)
//          w[i]=fp(i,i);
//      for(int i=1;i<=n;i++)
//          for(int j=1;j<=i;j++)
//              f[i]=(f[i]+f[i-j]*fac[i-1]%p*ifac[i-j]%p*ifac[j-1]%p*n%p*n%p*w[j]%p*ifacz)%p;

        for(int i=1;i<=n;i++)
            g[i]=fp(i,i)*n%p*n%p*ifac[i-1]%p*ifacz%p*inv[i]%p;
        int k=1;
        while(k<=n)
            k<<=1;
        ntt::exp(g,f,k);
        ans=f[n]*fac[n]%p*fp(z,n)%p*fp(n,p-1-4)%p;
    }
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    io::get(n);
    io::get(_z);
    io::get(op);
    z=fp(_z,p-2);
    if(op==0)
        solve0::solve();
    else if(op==1)
        solve1::solve();
    else
        solve2::solve();
    ans=ans*fp(_z,n)%p;
    ans=(ans%p+p)%p;
    io::put(ans);
    return 0;
}

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

时间: 2024-10-29 05:18:13

【WC2019】数树 树形DP 多项式exp的相关文章

【BZOJ-3572】世界树 虚树 + 树形DP

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种

【BZOJ-2286】消耗战 虚树 + 树形DP

2286: [Sdoi2011消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2120  Solved: 752[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁

【BZOJ2616】SPOJ PERIODNI 笛卡尔树+树形DP

[BZOJ2616]SPOJ PERIODNI Description Input 第1行包括两个正整数N,K,表示了棋盘的列数和放的车数. 第2行包含N个正整数,表示了棋盘每列的高度. Output 包括一个非负整数,表示有多少种放置的方案,输出答案mod 1000000007后的结果即可. Sample Input 5 2 2 3 1 2 4 Sample Output 43 HINT 对于100%的数据,有 N≤500,K≤500,h[i] ≤1000000. 题解:一看题就感觉应该是单调

#290. 【ZJOI2017】仙人掌(数数+仙人掌+树形dp)

传送门 模拟赛的时候打了个表发现为一条链的时候答案是\(2^{n-2}\)竟然顺便过了第一个点 然后之后订正的时候强联通分量打错了调了一个上午 首先不难发现我们可以去掉所有在环上的边,那么就变成了一个森林,不同的树之间不可能有连边,那么只要所有树的答案乘起来就好了,只要在每一棵树内部树形\(dp\)即可 考虑对于\(u\),它的子树如何统计答案 我们强制子树必须得向外连一条边(显然最多只有一条),然后往上统计 如果子树里没有向外连边,每一棵子树的答案乘起来 如果向外连边的话,那么要把子树内的边两

[WC2019] 数树

Statement 有\(n\)个节点, 分别用红线,蓝线连成两棵树. 用\(y\)种颜色给节点染色, 规定如果一条边在两棵树中同时出现, 那么边两端的点的颜色必须相同. Task #1: 给定两棵树, 求染色方案. Task #2: 给定其中一棵树, 求对于另一棵树的每一种形态的染色方案数之和. Task #3: 两棵树的形态都没有确定, 求对于所有情况的染色方案数之和. (\(n\le 10^5\)) Solution Task #1 计算有多少条公共边即可. Task #2 如果有\(i\

BZOJ 2286 消耗战 (虚树+树形DP)

给出一个n节点的无向树,每条边都有一个边权,给出m个询问,每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接.最少的边权和是多少.(n<=250000,sigma(ki)<=500000) 考虑树形DP,我们令mn[i]表示i节点无法与1节点相连切除的最小权值.显然有mn[i]=min(E(fa,i),mn[fa]).大致就是i到1的简单路径上的最小边.我们对于每个询问.把询问的点不妨称为关键点.令dp[i]表示i节点不能与子树的关键点连接切掉的最小权值.那么有,如果son[i]

【基环树/树形DP】BZOJ1040-[ZJOI2008]骑士

[题目大意] 有n个骑士,给出他们的能力值和最痛恨的一位骑士.选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力,求战斗力的最大值. [思路] 首先yy一下,可以知道这是一个基环森林.我们可以用以下方法: 首先在每一棵基环树的环上任意找到一条边(用dfs来实现),记它的两个端点为u和v.然后删掉这条边(我这里用的方法是记录u,v在对方容器中的位置,并在后续操作中忽略这条边).由于u和v不能同时取,在删掉u和v之间

【环套树+树形dp】Bzoj1040 [ZJOI2008] 骑士

Description Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队.于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶.骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾.每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不

BZOJ 1040:[ZJOI2008]骑士(环套外向树+树形DP)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1040 [题目大意] 给出环套外向树森林,求最大权独立集. [题解] 我们对于每个连通块,找到环上的一条边拆开,对于边的两端分别做树形DP, 假设两端点位x和y,那么不包含x的dp值涵盖了是否包含y两种情况, 同理,以y为根的也是,因为边的两端不能同时取到,因此对于两者取最大值即可. 代码中f[x]表示包含x的dp值,g[x]表示不包含x的dp值. [代码] #include <cst