【题解】「WC2019」数树
NOI十合一
请先保证有足够的耐心看完本题解
首先,若两树的公共边有\(k\)条,则给予数方案总共有\(y^{n-k}\)种。我有一个很好的证明,可惜这里太小写不下。
这样就能轻松通过subtask1
了。
显然,subtask1
在引导我们向公共边的数量取思考。
设\(f(i)\)表示两棵树恰有\(i\)条公共边的树的方案数(注意,不是给予数方案数)。则
然而,直接求\(f(i)\)并不好求,于是可以设\(g(i)\)表示两棵树有\(i\)条确定的公共边的树的方案数。可以得到,
可以由每一个恰有\(j\)条边的方案中选择\(i\)条边推得。
观察到\(f(j)\)乘的组合数都是\(C_j^*\)类型的,于是就有一个神奇的变换:
那么,\(g(i)\)该怎么求呢?
对于subtask2
,设选出来的\(i\)条公共边构成的\(n-i\)个连通块大小分别为\(a_1,a_2,\cdots,a_{n-i}\),则有:
证明:将每个连通块缩点,再进行生成树。设树边由儿子连向父亲,则prufer
序的每一位都可以选择任意一块,每一块都可以选择任意一个点,这样入边总共产生了\(n^{n-i-2}\)的贡献。再乘上出边的\(\prod a_j\)的贡献。
所以,设\(p=\frac 1y-1\),有:
设\(v=\frac np\),根据上面的方程,很容易列出一个简单的\(dp\),设\(dp_{i,j}\)表示,当前讨论了以\(i\)号点为根的子树,该子树内还有\(j\)个点没被划进公共边连通块内。显然转移是一个卷积式,可以设\(h_i(x)\)为\(i\)号点的生成函数。列出方程:
容易发现,\(ans=y^np^nn^{-2}dp_{1,0}=y^np^nn^{-2}v\times h_1'(1)\),所以,计算时只用到了\(h_i(1)\)与\(h_i'(1)\)。直接套用式子
\(O(n\log998244353)\)搞定。
接着看subtask3
,求\(g(i)\)的方法类似上一个子任务。不过结果有点不同:
很简单,就是将之前的\(g(i)\)平方,然后对每一个连通块再乘一个生成树数量\(a_j^{a_j-2}\)。
但是在数连通块的时候很容易数重,可以把所有连通块先按照大小排序,再按照最小节点编号排序。设大小为\(a\)的连通块有\(t_a\)个,则
设\(v_a=\frac{a^an^2}{a!p}\),则
又由于\(\sum t_aa=n\),设
则
先处理出\(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();
}