[WC2019] 数树
综合性非常强的一道题。。。
给定两棵树
发现题目其实是 的两颗树的边集交集形成的森林的连通块数次方,map
搞一搞就行。
给定一棵树
考虑枚举两个边集的交集,根据森林的连通块数可表达为点减边,可知答案为 ,其中 表示交集边数为 的第二棵树树的数量,发现 不好直接计算,是因为我们无法保证这 条边之外的边不在交集中,所以我们只能算出至少,再通过别的方法得到恰好。这里有两种解决方法:
第一种:考虑 表示钦定 条边属于交集之后第二棵树的数量,显然有:
根据二项式反演得:
那么答案就是:
第二种:上面的式子无法使用普通的容斥,是因为有一个 在。我们首先简单化一化式子:
考虑 ,我们直接去算至少,一个钦定了 条边的方案我们让它算 的贡献,最后再乘回 就是正确答案了。
其实两种方法最后的式子都是一样的。
考虑计算 ,对于一种选边的方案,最后可生成的树的数量可以由 prufer 序列得到,即 ,其实现在已经可以树形 DP 了,我们只需要对于选边的方案进行 DP,每选一条边额外乘上 就可以了。对于 ,朴素的想法是记一下现在点所在的连通块大小,但是 相当于在每个连通块选一个点的方案,所以我们只需用记 表示做到 ,目前连通块是否选了的答案,时间复杂度 。
不给定树
首先还是枚举交集,然后像上面那样处理成至少,答案就是:
注意:这里 的定义变成了钦定 条边后,两棵树的方案数。
这里没有树形态的限制,考虑直接写出 的通项:
解释一下,一开始先枚举每个连通块大小, 把点放进去,但是连通块之间无序,所以除以 , 是连通块内树的方案,同时连通块内点无序,除以 ,最后是剩下的生成两棵树的方案。
我们把这个带回去,设 :
既然有 ,不妨把后面的东西写成卷积的形式。
所以只需要做一遍多项式 EXP 就可以解决此题啦。
#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<map>
#include<vector>
#define N 100005
#define M 530000
#define mo 998244353
#define int long long
using namespace std;
inline int read(){
int x=0,f=0; char ch=getchar();
while(!isdigit(ch)) f|=(ch==45),ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
inline int qpow(int x,int b){
int res=1;
for(;b;x=x*x%mo,b>>=1) if(b&1) res=res*x%mo;
return res;
}
int n,y,op;
namespace sub0{
int fa[N];
map<pair<int,int>,int> MP;
int find(int x){
return x==fa[x]?x:(fa[x]=find(fa[x]));
}
inline void merge(int x,int y){
x=find(x),y=find(y);
if(x==y) return;
fa[x]=y;
}
void main(){
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<n;++i){
int x=read(),y=read();
if(x>y) swap(x,y);
MP[{x,y}]++;
}
for(int i=1;i<n;++i){
int x=read(),y=read();
if(x>y) swap(x,y);
if(MP[{x,y}]) merge(x,y);
}
int ans=1;
for(int i=1;i<=n;++i){
if(i==find(i)) ans=ans*y%mo;
}
printf("%lld\n",ans);
}
}
namespace sub1{
int f[N][2],C;
vector<int> G[N];
void dfs(int u,int fa){
f[u][0]=f[u][1]=1;
for(int v:G[u]){
if(v==fa) continue;
dfs(v,u);
int tmp0=f[u][0],tmp1=f[u][1];
f[u][0]=(f[v][1]*tmp0%mo+tmp0*f[v][0]%mo*C%mo)%mo;
f[u][1]=(f[v][1]*tmp1%mo+tmp0*f[v][1]%mo*C%mo+tmp1*f[v][0]%mo*C%mo)%mo;
}
}
void main(){
for(int i=1;i<n;++i){
int x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
}
C=(qpow(y,mo-2)-1)*qpow(n,mo-2)%mo;
dfs(1,0);
printf("%lld",f[1][1]*qpow(y,n)%mo*qpow(n,n-2)%mo);
}
}
namespace sub2{
inline void clear(int *A,int x,int y){for(int i=x;i<y;++i) A[i]=0;}
inline void copy(int *A,int *B,int x,int y){for(int i=x;i<y;++i) A[i]=B[i];}
inline void mul(int *A,int *B,int x,int y){for(int i=x;i<y;++i) A[i]=A[i]*B[i]%mo;}
int cir[M],rt;
inline void NTT(int *f,int len,int tag){
if(len!=rt) for(int i=0;i<len;++i) cir[i]=(cir[i>>1]>>1)|((i&1)?len>>1:0);
rt=len;
for(int i=0;i<len;++i) if(i<cir[i]) swap(f[i],f[cir[i]]);
for(int l=2;l<=len;l<<=1){
int sze=l>>1,buf=qpow(tag?3:332748118,(mo-1)/l);
for(int i=0;i<len;i+=l){
int bas=1;
for(int j=i;j<i+sze;++j,bas=bas*buf%mo){
int tmp=bas*f[j+sze]%mo;
f[j+sze]=(f[j]-tmp+mo)%mo,(f[j]+=tmp)%=mo;
}
}
}
if(tag==1) return;
int invn=qpow(len,mo-2);
for(int i=0;i<len;++i) f[i]=f[i]*invn%mo;
}
int tmul[M];
inline void polyMul(int *A,int *B,int len1,int len2){
#define sav tmul
int len=1;
while(len<(len1<<1)) len<<=1;
copy(sav,B,0,len);
NTT(A,len,1),NTT(sav,len,1),mul(A,sav,0,len),NTT(A,len,0);
clear(A,len2,len),clear(sav,0,len);
#undef sav
}
int inv1[M],inv2[M],inv3[M];
inline void polyInv(int *A,int len){
#define sav1 inv1
#define sav2 inv2
#define sav3 inv3
int lim=1;while(lim<len) lim<<=1;
sav1[0]=qpow(A[0],mo-2);
for(int l=2;l<=lim;l<<=1){
int r=l<<1;copy(sav3,A,0,l);
for(int i=0;i<(l>>1);++i) sav2[i]=(sav1[i]<<1)%mo;
NTT(sav1,r,1),mul(sav1,sav1,0,r);
NTT(sav3,r,1),mul(sav1,sav3,0,r);
NTT(sav1,r,0),clear(sav1,l,r);
for(int i=0;i<l;++i) sav1[i]=(sav2[i]-sav1[i]+mo)%mo;
}
copy(A,sav1,0,len);
clear(sav1,0,lim<<1),clear(sav2,0,lim<<1),clear(sav3,0,lim<<1);
#undef sav1
#undef sav2
#undef sav3
}
inline void polyDer(int *A,int len){
for(int i=1;i<len;++i) A[i-1]=A[i]*i%mo;A[len-1]=0;
}
inline void polyInt(int *A,int len){
for(int i=len-1;i>=1;--i) A[i]=A[i-1]*qpow(i,mo-2)%mo;A[0]=0;
}
int ln1[M];
inline void polyLn(int *A,int len){
#define sav ln1
copy(sav,A,0,len),polyDer(A,len),polyInv(sav,len);
polyMul(A,sav,len,len),polyInt(A,len),clear(sav,0,len);
#undef sav
}
int exp1[M],exp2[M];
inline void polyExp(int *A,int len){
#define sav1 exp1
#define sav2 exp2
int lim=1;while(lim<len) lim<<=1;
sav1[0]=1;
for(int l=2;l<=lim;l<<=1){
copy(sav2,sav1,0,l>>1),polyLn(sav2,l);
for(int i=0;i<l;++i) sav2[i]=(A[i]-sav2[i]+mo)%mo;
sav2[0]=(sav2[0]+1)%mo;
polyMul(sav1,sav2,l,l);
}
copy(A,sav1,0,len),clear(sav1,0,lim),clear(sav2,0,lim);
#undef sav1
#undef sav2
}
int fac[N],ifac[N],F[M];
void main(){
if(y==1) return (void)(printf("%lld",qpow(n,2*(n-2))));
int ny=qpow(y,mo-2)-1,iny=qpow(ny,mo-2);
fac[0]=1;for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mo;
ifac[n]=qpow(fac[n],mo-2);
for(int i=n;i>=1;--i) ifac[i-1]=ifac[i]*i%mo;
for(int i=1;i<=n;++i) F[i]=n*n%mo*iny%mo*qpow(i,i)%mo*ifac[i]%mo;
polyExp(F,n+1);
printf("%lld\n",fac[n]*qpow(ny,n)%mo*qpow(n,mo-5)%mo*F[n]%mo*qpow(y,n)%mo);
}
}
signed main(){
n=read(),y=read(),op=read();
if(op==0) sub0::main();
if(op==1) sub1::main();
if(op==2) sub2::main();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现