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 imes h_1'(1)),所以,计算时只用到了(h_i(1))与(h_i'(1))。直接套用式子
(O(nlog998244353))搞定。
接着看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(nlog 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
",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();
}