2018.10.30【校内模拟】排列树(组合数学)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/83544663

传送门


解析:

考场上没想到是组合数学,瞎打了一个 O ( n ! ) O(n!) 暴力滚粗了。

但是下来一看真的是组合数学sb题。。。

思路:

首先我们不要考虑标号的值域,我们只需要求出节点标号之间有多少可能的偏序关系,而不是求出实际的标号有多少种分配方式。

考虑我们现在求出了某个子树的标号偏序方案数。
那么我们需要合并 u u 两个子树来更新现在的以 u u 为根的子树的偏序方案数。

首先根据乘法原理,不考虑排列,他们分别的偏序方案数对总方案的贡献就是乘积。

然后考虑合并,设我们之前已经合并的子树总大小为 n n ,现在需要新加入的子树大小为 m m

实际上就是在两个队列里面一直取队首的操作,实际贡献就是 C n + m m C_{n+m}^m

怎么理解呢。我们已经确定了子树内部的偏序关系,由于题目要求是严格偏序,所以可以用一个排好序的队列来存,而合并就是归并排序式的合并就行了。

考虑我们已经完成的最终长度为 n + m n+m 的队列,而其中任取 m m 个位置都可能放着原来队列中的元素,所以方案数就是组合数。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define int ll

inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

cs ll mod=998244353;
cs int N=100005;
int last[N],nxt[N<<1],to[N<<1],ecnt;
inline void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int fac[N],inv[N],ifac[N];
inline int C(int n,int m){
	return fac[n]*1ll*ifac[m]%mod*ifac[n-m]%mod;
}

ll g[N];
int siz[N];
inline void dfs(int u,int fa){
	g[u]=1;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa)continue;
		dfs(v,u);
		siz[u]+=siz[v];
		g[u]=g[u]*g[v]%mod;
		g[u]=g[u]*C(siz[u],siz[v])%mod;
	}++siz[u];
}

int n;
signed main(){
	fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1;
	for(int re i=2;i<N;++i){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		ifac[i]=ifac[i-1]*1ll*inv[i]%mod; 
	}
	n=getint();
	for(int re i=1;i<n;++i){
		int u=getint(),v=getint();
		addedge(u,v);
	}
	dfs(1,0);
	cout<<g[1];
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/83544663
今日推荐