1. 程式人生 > >BZOJ 5475: [WC 2019] 數樹

BZOJ 5475: [WC 2019] 數樹

鏈接 stream void pow ace sca 相同 mit 展開

WC2019 數樹

  • 解決了一個心頭大患
  • 考試的時候本人太智障了QwQ
  • 本文的參考鏈接,膜了一發rqy的題解

題目鏈接

Subtask 0

好像可以直接做...

推一推就能發現,是$y^k$,其中$k$表示相同的邊構成的聯通塊數...

(我在考試的時候,絲毫都沒有意識到這是$n-邊數$

namespace Subtask1
{
    int main()
    {
        int cnt=n;
        for(int x=1;x<=n;x++)
        {
            for(int i=head[x];i!=-1;i=e[i].next)
            {
                int to1=e[i].to;
                if(to1<x&&mp[x].find(to1)!=mp[x].end())cnt--;
            }
        }
        printf("%d\n",q_pow(y,cnt));
        return 0;
    }
}

Subtask 1

這個就很麻煩了qwq

我們知道,$ans=y^{n-|E_1 \cap E_2|}$

通過簡單容斥原理,我們可以得到:$f(S)=\sum\limits_{T\subseteq S}\sum\limits_{P\subseteq T}(-1)^{|T|-|P|}\times f(P)$

對於這樣的問題,有一個簡易的想法,就是枚舉相同邊集,也就是$f(S)=y^{n-|S|},S=E_1\cap E_2$

那麽答案就是:$\sum\limits_{E_2}y^{n-|E_1\cap E_2|}=\sum\limits_{E_2}\sum\limits_{S\subseteq|E_1 \cap E_2|}\sum\limits_{P\subseteq S}(-1)^{|S|-|P|}\times y^{n-|P|}$

那麽考慮變換枚舉順序:$\sum\limits_{S\subseteq E_1}(\sum\limits_{P\subseteq S}(-1)^{|S|-|P|}\times y^{n-|P|})\sum\limits_{E_2}[S\subseteq E_2]$

對於最後一項,表達的是包含$S$邊集的生成樹的個數,根據Prufer序列凱萊公式可以得到:$\sum\limits_{S\subseteq E_1}(n^{n-|S|-2}\times \prod\limits_{i=1}^{n-|S|} a_i) \sum\limits_{P\subseteq S}(-1)^{|S|-|P|}\times y^{n-|P|}$,其中$a_i$表示每個聯通塊的大小

為了方便表示,我們設:$g(S)=n^{n-|S|-2}\times\prod\limits_{i=1}^K a_i $

然後考慮如何去掉$P$的枚舉,對於$(1-y)^k=\sum\limits_{i=0}^k C(k,i)\times (-1)^i\times y^i=\sum\limits_{S\subseteq T}(-y)^{|S|}$,其中$|T|=k$

所以我們考慮將$y^{n-|P|}$轉化一下得到:$\sum\limits_{S\subseteq E_1}g(S)\times y^{n-|S|}\sum\limits_{P\subseteq S}(-y)^{|S|-|P|}$

那麽對於上面的式子可以變成:$\sum\limits_{S\subseteq E_1} g(S)\times y^{n-|S|}\times (1-y)^{|S|}$

然後展開$g(S)$:$\sum\limits_{S\subseteq E_1} n^{n-|S|-2}\times y^{n-|S|}\times (1-y)^{|S|} \prod\limits_{i=1}^{n-|S|} a_i $

設:$n-|S|=k$

那麽:$\sum\limits_{S\subseteq E_1}n^{k-2}\times y^k\times (1-y)^{n-k}\times \prod\limits_{i=1}^ka_i$

然後把$(1-y)^{n-k},n^{k-2}$轉化一下,提出去可以得到:$\frac{(1-y)^n}{n^2}\sum\limits_{S\subseteq E_1} (\frac{n\times y}{1-y})^k \prod\limits_{i=1}^k a_i$

然後設:$K=\frac{n\times y}{1-y}$,忽略掉前面的常數:$\sum\limits_{S\subseteq E_1}K^k \prod\limits_{i=1}^k a_i=\sum\limits_{S\subseteq E_1}\prod\limits_{i=1}^k a_i\times K$

那麽相當於是對於每個聯通塊有大小乘以$K$的貢獻,這個東西顯然可以樹形背包...

但是這樣是$n^2$的,非常講道理...

那我們考慮背包的多項式有沒有什麽神奇的性質...

對於答案,我們設:$g(x)=K\sum\limits_{i=1} i\times f_x(i)$

設:$F_p(x)=\sum\limits_{i=1}x^i\times f_p(i)$

那麽顯然$g(x)=K\times F_x‘(1)$

對於$F_u(x)$的轉移很顯然,$F_u(x)=x\prod\limits_{v}(F_v(x)+g(v))$

那麽根據導數的定義:$g(u)=K\times \prod\limits_{v}(F_v(1)+g(v))+(\prod\limits_{v} (F_v(1)+g(v)))\sum\limits_{v}\frac{K\times F_v‘(1)}{g(v)+F_v(1)}$

那麽我們發現,如果需要維護$g(x)$那麽只需要維護$F_x(1)$即可。

所以設:$f(x)=F_x(1)$,因此:$g(u)=f(u)\times (K+\sum\limits_{v} \frac{g(v)}{g(v)+f(v)})$

那麽就完事了qwq

namespace Subtask2
{
    int f[N],g[N],K;
    void dfs(int x,int from)
    {
        g[x]=K,f[x]=1;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from)
            {
                dfs(to1,x);
                g[x]=(g[x]+(ll)g[to1]*inv(g[to1]+f[to1]))%mod;
                f[x]=(ll)f[x]*(g[to1]+f[to1])%mod;
            }
        }
        g[x]=(ll)g[x]*f[x]%mod;
    }
    int main()
    {
        if(y==1)return printf("%d\n",q_pow(n,n-2)),0;
        K=(ll)n*y%mod*inv(1-y)%mod;dfs(1,0);
        printf("%lld\n",((ll)g[1]*q_pow(n,mod-3)%mod*q_pow(1-y,n)%mod+mod)%mod);
        return 0;
    }
}

Subtask 2

根據上面的容斥原理,我們想到一個絕妙的方法,那就是倆都枚舉一下試試

$\sum\limits_{S}g(S)^2 \times y^{n-|S|}\times (1-y)^{|S|}$

然後同樣展開qwq

$\frac{(1-y)^n}{n^4}\sum\limits_S \prod\limits_{i=1}^k a_i^2 \frac{n^2 \times y}{1-y}$

然後同樣設:$K=\frac{n^2\times y}{1-y}$

同樣發現,可以得到:$\frac{(1-y)^n}{n^4}\sum\limits_{S}\prod\limits_{i=1}^k a_i^2\times K$

然後.........麻麻我不會了..........

那麽證明,我們不能通過枚舉邊集來解決...

所以,我們考慮所有的聯通塊具有什麽樣的性質,顯然,他們所有聯通塊的點數之和恰好為$n$,好吧,這是一句廢話qwq

對於這樣的形式,我們可以考慮枚舉每個聯通塊也就是說,我們可以得到:$\frac{(1-y)^n}{n^4}\sum\limits_{(\sum\limits_{i=1}^k a_i)=n}\frac{1}{k!}\prod\limits_{i=1}^k \frac{a_i^{a_i-2}}{a_i!}\times a_i^2\times K$

那麽稍微化簡化簡:$\frac{(1-y)^n}{n^4}\sum\limits_{(\sum\limits_{i=1}^k a_i)=n}\frac{1}{k!}\prod\limits_{i=1}^k K \times\frac{a_i^{a_i}}{a_i!}$

我們發現:多項式出現了!

$\frac{(1-y)^n}{n^4}x^n$,其中,$f(x)=\sum\limits_{i=1} \frac{i^i}{i!} x^i$

然後可以發現,前面的式子就是:$\frac{(1-y)^n}{n^4}x^n$

然後,對於$e^x$麥克勞林展開:$\sum\limits_{i=0} \frac{x^k}{i!}$

那麽對於後面的那個式子,本質就是$e^{f(x)}$

那麽直接多項式exp就好了qwq

namespace Subtask3
{
    int A[N<<2],B[N<<2];
    void NTT(int *a,int len,int flag)
    {
        int i,j,k,t,w,x,tmp;
        for(i=k=0;i<len;i++)
        {
            if(i>k)swap(a[i],a[k]);
            for(j=len>>1;(k^=j)<j;j>>=1);
        }
        for(k=2;k<=len;k<<=1)
        {
            t=k>>1;x=q_pow(3,(mod-1)/k);if(flag==-1)x=inv(x);
            for(i=0;i<len;i+=k)
                for(w=1,j=i;j<i+t;j++,w=(ll)w*x%mod)
                    tmp=(ll)w*a[j+t]%mod,a[j+t]=(a[j]-tmp)%mod,a[j]=(a[j]+tmp)%mod;
        }if(flag==-1)for(t=inv(len),i=0;i<len;i++)a[i]=(ll)a[i]*t%mod;
    }
    struct poly
    {
        vector<int >a;int len;
        poly(){a.clear();len=0;}
        poly(int x){a.clear();a.push_back(x);len=1;}
        void resize(int x){a.resize(x);for(int i=len;i<x;i++)a[i]=0;len=x;}
        poly operator * (const poly &b) const 
        {
            poly c=poly();c.resize(len+b.len-1);
            if(c.len<=200)
            {
                for(int i=0;i<len;i++)
                    for(int j=0;j<b.len;j++)
                        c.a[i+j]=(c.a[i+j]+(ll)a[i]*b.a[j])%mod;
                return c;
            }
            int n=1,i;while(n<c.len)n<<=1;
            for(i=0;i<len;i++)A[i]=a[i];for(i=len;i<n;i++)A[i]=0;
            for(i=0;i<b.len;i++)B[i]=b.a[i];for(i=b.len;i<n;i++)B[i]=0;
            NTT(A,n,1);NTT(B,n,1);for(i=0;i<n;i++)A[i]=(ll)A[i]*B[i]%mod;NTT(A,n,-1);
            for(i=0;i<c.len;i++)c.a[i]=A[i];return c;
        }
        poly operator + (const poly &b) const
        {
            poly c=poly();c.resize(max(b.len,len));
            for(int i=0;i<len;i++)c.a[i]=a[i];
            for(int i=0;i<b.len;i++)(c.a[i]+=b.a[i])%=mod;
            return c;
        }
        poly operator - (const poly &b) const
        {
            poly c=poly();c.resize(max(b.len,len));
            for(int i=0;i<len;i++)c.a[i]=a[i];
            for(int i=0;i<b.len;i++)(c.a[i]-=b.a[i])%=mod;
            return c;
        }
        void get_inv(poly &b,int n)
        {
            if(n==1)return void(b=poly(inv(a[0])));get_inv(b,n>>1);int t=n<<1,lim=min(n,len);
            for(int i=0;i<lim;i++)A[i]=a[i];for(int i=lim;i<t;i++)A[i]=0;
            for(int i=0;i<b.len;i++)B[i]=b.a[i];for(int i=b.len;i<t;i++)B[i]=0;
            NTT(A,t,1);NTT(B,t,1);for(int i=0;i<t;i++)B[i]=(2-(ll)A[i]*B[i]%mod)*B[i]%mod;NTT(B,t,-1);
            b.resize(n);for(int i=0;i<n;i++)b.a[i]=B[i];
        }
        poly Dao(){poly c=poly();c.resize(len-1);for(int i=1;i<len;i++)c.a[i-1]=(ll)i*a[i]%mod;return c;}
        poly Int(){poly c=poly();c.resize(len+1);for(int i=0;i<len;i++)c.a[i+1]=(ll)a[i]*inv(i+1)%mod;return c;}
        poly get_ln(int n)
        {
            poly c=poly();get_inv(c,n);
            c=(c*Dao()).Int();c.resize(n);
            return c;
        }
        void print(){printf("lenth = %d\n",len);for(int i=0;i<len;i++)printf("%d ",a[i]);puts("");}
    }a,b;
    void get_exp(const poly &a,poly &b,int len)
    {
        if(len==1){b=poly(1);return ;}get_exp(a,b,len>>1);
        poly c=a-b.get_ln(len);c.a[0]++;c.resize(len);b=b*c;b.resize(len);
    }
    int fac[N],inv[N];
    int main()
    {
        if(y==1)return printf("%d\n",q_pow(n,2*(n-2))),0;
        fac[0]=1;for(int i=1;i<=n;i++)fac[i]=(ll)fac[i-1]*i%mod;
        inv[n]=inv(fac[n]);for(int i=n;i;i--)inv[i-1]=(ll)inv[i]*i%mod;
        int K=(ll)n*n%mod*y%mod*inv(1-y)%mod;a.resize(n+1);
        for(int i=1;i<=n;i++)a.a[i]=(ll)q_pow(i,i)*inv[i]%mod*K%mod;
        int len=1;while(len<(n+1))len<<=1;get_exp(a,b,len);
        printf("%lld\n",((ll)b.a[n]*fac[n]%mod*q_pow(n,mod-5)%mod*q_pow(1-y,n)%mod+mod)%mod);
        return 0;
    }
}

然後完整代碼如下:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
#include <bitset>
#include <map>
using namespace std;
#define N 100005
#define ll long long
#define mod 998244353
struct node{int to,next;}e[N<<1];
int n,head[N],y,op,cnt;
void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;}
int q_pow(int x,int n){int ret=1;for(;n;n>>=1,x=(ll)x*x%mod)if(n&1)ret=(ll)ret*x%mod;return ret;}
#define inv(x) q_pow(x,mod-2)
map<int ,int >mp[N];
namespace Subtask1
{
    int main()
    {
        int cnt=n;
        for(int x=1;x<=n;x++)
        {
            for(int i=head[x];i!=-1;i=e[i].next)
            {
                int to1=e[i].to;
                if(to1<x&&mp[x].find(to1)!=mp[x].end())cnt--;
            }
        }
        printf("%d\n",q_pow(y,cnt));
        return 0;
    }
}
namespace Subtask2
{
    int f[N],g[N],K;
    void dfs(int x,int from)
    {
        g[x]=K,f[x]=1;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from)
            {
                dfs(to1,x);
                g[x]=(g[x]+(ll)g[to1]*inv(g[to1]+f[to1]))%mod;
                f[x]=(ll)f[x]*(g[to1]+f[to1])%mod;
            }
        }
        g[x]=(ll)g[x]*f[x]%mod;
    }
    int main()
    {
        if(y==1)return printf("%d\n",q_pow(n,n-2)),0;
        K=(ll)n*y%mod*inv(1-y)%mod;dfs(1,0);
        printf("%lld\n",((ll)g[1]*q_pow(n,mod-3)%mod*q_pow(1-y,n)%mod+mod)%mod);
        return 0;
    }
}
namespace Subtask3
{
    int A[N<<2],B[N<<2];
    void NTT(int *a,int len,int flag)
    {
        int i,j,k,t,w,x,tmp;
        for(i=k=0;i<len;i++)
        {
            if(i>k)swap(a[i],a[k]);
            for(j=len>>1;(k^=j)<j;j>>=1);
        }
        for(k=2;k<=len;k<<=1)
        {
            t=k>>1;x=q_pow(3,(mod-1)/k);if(flag==-1)x=inv(x);
            for(i=0;i<len;i+=k)
                for(w=1,j=i;j<i+t;j++,w=(ll)w*x%mod)
                    tmp=(ll)w*a[j+t]%mod,a[j+t]=(a[j]-tmp)%mod,a[j]=(a[j]+tmp)%mod;
        }if(flag==-1)for(t=inv(len),i=0;i<len;i++)a[i]=(ll)a[i]*t%mod;
    }
    struct poly
    {
        vector<int >a;int len;
        poly(){a.clear();len=0;}
        poly(int x){a.clear();a.push_back(x);len=1;}
        void resize(int x){a.resize(x);for(int i=len;i<x;i++)a[i]=0;len=x;}
        poly operator * (const poly &b) const 
        {
            poly c=poly();c.resize(len+b.len-1);
            if(c.len<=200)
            {
                for(int i=0;i<len;i++)
                    for(int j=0;j<b.len;j++)
                        c.a[i+j]=(c.a[i+j]+(ll)a[i]*b.a[j])%mod;
                return c;
            }
            int n=1,i;while(n<c.len)n<<=1;
            for(i=0;i<len;i++)A[i]=a[i];for(i=len;i<n;i++)A[i]=0;
            for(i=0;i<b.len;i++)B[i]=b.a[i];for(i=b.len;i<n;i++)B[i]=0;
            NTT(A,n,1);NTT(B,n,1);for(i=0;i<n;i++)A[i]=(ll)A[i]*B[i]%mod;NTT(A,n,-1);
            for(i=0;i<c.len;i++)c.a[i]=A[i];return c;
        }
        poly operator + (const poly &b) const
        {
            poly c=poly();c.resize(max(b.len,len));
            for(int i=0;i<len;i++)c.a[i]=a[i];
            for(int i=0;i<b.len;i++)(c.a[i]+=b.a[i])%=mod;
            return c;
        }
        poly operator - (const poly &b) const
        {
            poly c=poly();c.resize(max(b.len,len));
            for(int i=0;i<len;i++)c.a[i]=a[i];
            for(int i=0;i<b.len;i++)(c.a[i]-=b.a[i])%=mod;
            return c;
        }
        void get_inv(poly &b,int n)
        {
            if(n==1)return void(b=poly(inv(a[0])));get_inv(b,n>>1);int t=n<<1,lim=min(n,len);
            for(int i=0;i<lim;i++)A[i]=a[i];for(int i=lim;i<t;i++)A[i]=0;
            for(int i=0;i<b.len;i++)B[i]=b.a[i];for(int i=b.len;i<t;i++)B[i]=0;
            NTT(A,t,1);NTT(B,t,1);for(int i=0;i<t;i++)B[i]=(2-(ll)A[i]*B[i]%mod)*B[i]%mod;NTT(B,t,-1);
            b.resize(n);for(int i=0;i<n;i++)b.a[i]=B[i];
        }
        poly Dao(){poly c=poly();c.resize(len-1);for(int i=1;i<len;i++)c.a[i-1]=(ll)i*a[i]%mod;return c;}
        poly Int(){poly c=poly();c.resize(len+1);for(int i=0;i<len;i++)c.a[i+1]=(ll)a[i]*inv(i+1)%mod;return c;}
        poly get_ln(int n)
        {
            poly c=poly();get_inv(c,n);
            c=(c*Dao()).Int();c.resize(n);
            return c;
        }
        void print(){printf("lenth = %d\n",len);for(int i=0;i<len;i++)printf("%d ",a[i]);puts("");}
    }a,b;
    void get_exp(const poly &a,poly &b,int len)
    {
        if(len==1){b=poly(1);return ;}get_exp(a,b,len>>1);
        poly c=a-b.get_ln(len);c.a[0]++;c.resize(len);b=b*c;b.resize(len);
    }
    int fac[N],inv[N];
    int main()
    {
        if(y==1)return printf("%d\n",q_pow(n,2*(n-2))),0;
        fac[0]=1;for(int i=1;i<=n;i++)fac[i]=(ll)fac[i-1]*i%mod;
        inv[n]=inv(fac[n]);for(int i=n;i;i--)inv[i-1]=(ll)inv[i]*i%mod;
        int K=(ll)n*n%mod*y%mod*inv(1-y)%mod;a.resize(n+1);
        for(int i=1;i<=n;i++)a.a[i]=(ll)q_pow(i,i)*inv[i]%mod*K%mod;
        int len=1;while(len<(n+1))len<<=1;get_exp(a,b,len);
        printf("%lld\n",((ll)b.a[n]*fac[n]%mod*q_pow(n,mod-5)%mod*q_pow(1-y,n)%mod+mod)%mod);
        return 0;
    }
}
int main()
{
    scanf("%d%d%d",&n,&y,&op);
    if(op==2)return Subtask3::main();memset(head,-1,sizeof(head));
    for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x);
    if(op==1)return Subtask2::main();
    for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),mp[x][y]=mp[y][x]=1;
    if(op==0)return Subtask1::main();
}

如果知道這個題的Subtask 2是多項式exp還行,如果不知道的話,硬想很難的qwq

然後Subtask 1的容斥才是本題的關鍵,Subtask 2算是錦上添花吧qwq

總體上,本題考查了$\min-\max$容斥(你仔細看看容斥式子,就是這個

考察了優化DP的一些技巧,同時搭配$prufer$序列的知識。

最後還考察了多項式exp的巧妙轉化

是對選手數學功底的一個考驗,顯然對於我這種菜雞就是挑戰了qwq

然後還有一些奇妙的性質,總之,這是一個非常優秀的計數題目

最後,$Orz ??rqy$

BZOJ 5475: [WC 2019] 數樹