首页 > 其他分享 >数树题

数树题

时间:2024-06-06 20:44:43浏览次数:11  
标签:int back poly ++ push 数树题 tmp2

数树题。

[ARC155F] Directable as Desired

给定长度为 \(N\) 的非负整数序列 \(D=(D_1,D_2,\dots,D_N)\),满足 \(\sum_{i=1}^N D_i=N-1\)。

统计有多少带标号无根树,节点编号 \(1 \sim N\),满足以下条件:

  • 存在一种将 \(N-1\) 条边分别定向的方案,使得节点 \(i\) 的出度为 \(D_i\)。

输出答案对 \(998244353\) 取模后的结果。

数据范围:\(2 \le N \le 2 \times 10^5\)。

有标号无根树比较抽象,所以转化成点边均有标号的有根树求解。

一棵有标号无根树转化为点边均有标号的有根树的方案数是 \(n\prod d_i\),留着备用。

把根搞出来后,每条边的方向就分成两种,一种是向上即向根节点方向、一种是向下即向叶子节点方向。

考虑指定一个集合 \(S\) 满足仅有 \(S\) 节点与其父亲节点的边是向上的。设 \(m = |S|\)。

然后指定这些点是哪条边连向父亲节点,这里的方案数是 \(\prod_{i \in S} d_i\)。

那么现在我们依次考虑指定向上的边的方案数和向下的边的方案数。

先看向上的边。因为仅对于 \(S\) 中的点才会产生一条这样的边,所以最后会形成的是 \(n\) 个点 \(m\) 条边的内向树森林。

森林不好算,所以指定一个超级点 \(0\),让所有内向树根连向 \(0\)。

这样就是要对 \(0\) 的度数为 \(n - m\) 的内向树计数。

把边的方向忽视后就是对 \(0\) 节点度数为 \(n - m\) 的无根树计数。

转化为 Prufer 序列后就是 \(0\) 号节点出现 \(n - m - 1\) 次的序列计数,这一部分的答案为:

\[n^m \binom {n - 1}{n - m - 1} \]

但是这个答案包含了指定 \(S\) 集合的方案数,而到这一步我们的 \(S\) 集合是已知的,所以要除以一个 \(\binom nm\)。

所以这一部分的答案为

\[n ^ {m - 1} (n - m) \]

最后考虑向下的边的方案数。

因为每条边都有编号,所以按照编号依次连边。

经过了上面的步骤我们得到了 \(n\) 个点 \(m\) 条边的内向树森林。每条边不能连向自己的祖先,不能连向向上连了边的点。连第一条边时有 \(n - m - 1\) 种方案,然后此时连通块少了一个。以此类推,连第 \(i\) 条边的时候我们有 \(n - m - i\) 种选择。综上这一部分的方案数是

\[(n - m - 1)! \]

把这三部分的答案整合起来得到点边标号的有根树的答案:

\[\sum _{m = 0} ^{n - 1} (n - m - 1)! \cdot n ^ {m - 1} (n - m) \cdot \sum _{|S| = m} \prod _ {i \in S} d_i \\ =\sum_{m = 0} ^{n - 1} (n - m) ! \cdot n ^ {m - 1} \cdot [x^m] \prod _{i = 1} ^n (1 + d_ix) \]

后面的部分分治 NTT 即可。最后答案除以 \(n\prod d_i\) 就行了。

#include<iostream>
#include<fstream>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
namespace Poly{
	using poly = vector<int>;
	const int G = 3, P = 998244353;
	const int inv2 = 499122177;
	int Ksm(int u, int v){
		int ret = 1;
		while(v){
			if(v & 1) ret = 1ll * ret * u % P;
			u = 1ll * u * u % P, v >>= 1;
		}
		return ret;
	}
	int NTT(poly &A, int N){
		int d = N >> 1;
		poly trn;
		trn.push_back(0);
		trn.push_back(d);
		for(int w = 2; w <= N; w <<= 1){
			d >>= 1;
			for(int c = 0; c < w; c ++)
				trn.push_back(trn[c] | d);
		}
		for(int i = 1; i < N; i ++)
			if(trn[i] > i)
				swap(A[i], A[trn[i]]);
		for(int len = 2, M = 1; len <= N; M = len, len <<= 1){
			int W;
			W = Ksm(G, (P - 1) / len);
			for(int l = 0; l <= N - len + 1; l += len){
				auto w0 = 1;
				for(int nw = l; nw < l + M; nw ++){
					int x = A[nw], y = 1ll * w0 * A[nw + M] % P;
					A[nw] = (x + y) % P, A[nw + M] = (x - y + P) % P;
					w0 = 1ll * w0 * W % P;
				}
			}
		}
		return 0;
	}
	poly convolution(poly a, poly b){
		int n = a.size(), m = b.size();
		if(!n || !m)
			return {};
		int sm = n + m - 1;
		int k = 1;
		while(k < sm)
			k <<= 1;
		a.resize(k), b.resize(k);
		NTT(a, k);
		NTT(b, k);
		for(int i = 0; i < k; i ++)
			a[i] = 1ll * a[i] * b[i] % P;
		NTT(a, k);
		reverse(++ a.begin(), a.end());
		int invk = Ksm(k, P - 2);
		for(int i = 0; i < a.size(); i ++)
			a[i] = 1ll * a[i] * invk % P;
		a.resize(n + m - 1);
		return a;
	}
	poly polyInv(poly f){
		int n = f.size();
		int k = 1;
		while(k < n)
			k <<= 1;
		k <<= 1;
		poly g;
		f.resize(k);
		g.push_back(Ksm(f[0], P - 2));
		for(int len = 2; len <= k; len <<= 1){
			int M = len >> 1;
			poly tmp2, tmp1;
			for(int i = 0; i < M; i ++)
				tmp2.push_back(g[i]);
			for(int i = 0; i < M; i ++)
				tmp1.push_back(f[i]);
			tmp1.resize(len);
			tmp2 = convolution(tmp2, tmp2);
			tmp2 = convolution(tmp1, tmp2);
			for(int i = 0; i < M; i ++)
				g[i] = (2ll * g[i] % P + P - tmp2[i]) % P;
			for(int i = M; i < len; i ++)
				g.push_back((2ll * g[i] % P + P - tmp2[i]) % P);
		}
		g.resize(n);
		return g;
	}
	poly polyDev(poly f){
		poly g;
		int k = f.size();
		for(int i = 1; i < k; i ++)
			g.push_back(1ll * i * f[i] % P);
		g.resize(k - 1);
		return g;
	}
	poly polyInDev(poly f){
		poly g;
		int k = f.size();
		g.push_back(0);
		for(int i = 1; i < k; i ++)
			g.push_back(1ll * f[i - 1] * Ksm(i, P - 2) % P);
		return g;
	}
	poly polyLn(poly a){
		int n = a.size();
		poly b = a;
		a = polyDev(a);
		b = polyInv(b);
		a = convolution(a, b);
		a = polyInDev(a);
		a.resize(n);
		return a;
	}
	int polyOutput(poly a){
		for(int i = 0; i < a.size(); i ++)
			cout << a[i] << " ";
		cout << "\n";
		return 0;
	}
	poly polyExp(poly f){
		int n = f.size();
		int k = 1;
		while(k < n)
			k <<= 1;
		k <<= 1;
		poly g;
		f.resize(k);
		g.push_back(1);
		for(int len = 2; len <= k; len <<= 1){
			int M = len >> 1;
			poly tmp2;
			for(int i = 0; i < M; i ++)
				tmp2.push_back(g[i]);
			auto tmp3 = polyLn(tmp2);
			for(int i = 0; i < M; i ++)
				tmp3[i] = (((P - tmp3[i] + f[i]) % P) + P) % P;
			tmp3[0] ++;
			tmp3[0] %= P;
			tmp2 = convolution(tmp3, tmp2);
			for(int i = 0; i < M; i ++)
				g[i] = tmp2[i];
			for(int i = M; i < len; i ++)
				g.push_back(tmp2[i]);
		}
		g.resize(n);
		return g;
	}
	poly polyPow(poly a, int k){
		int n = a.size();
		int t = 0;
		for(int i = 0; i < n; i ++){
			if(a[t] != 0) break;
			t ++;
		}
		if(t == n) return a;
		int r = Ksm(a[t], P - 2);
		poly b;
		for(int i = t; i < n; i ++)
			b.push_back(1ll * a[i] * r % P);
		a.resize(t);
		int m = b.size();
		b = polyLn(b);
		for(int i = 0; i < m; i ++)
			b[i] = 1ll * b[i] * k % P;
		b = polyExp(b);
		for(int i = 0; i < m; i ++)
			a.push_back(b[i]);
		return a;
	}
}
namespace azus{
	using namespace Poly;
	int n;
	int d[200005];
	poly a;
	vector<int> calc(vector<int> &a, int l, int r) {
		if (r - l == 1)
			return {1, a[l]};
		int mid = (l + r) >> 1;
		return convolution(calc(a, l, mid), calc(a, mid, r));
	}
	int ans = 0;
	int jc[200005], inv[200005];
	int main(){
		cin >> n;
		jc[0] = inv[0] = 1;
		for(int i = 1; i <= n; i ++)
			jc[i] = jc[i - 1] * i % P;
		inv[n] = Ksm(jc[n], P - 2);
		for(int i = n - 1; i >= 1; i --)
			inv[i] = inv[i + 1] * (i + 1) % P;
		for(int i = 1; i <= n; i ++)
			cin >> d[i], a.push_back(d[i]);
		a = calc(a, 0, n);
		ans = jc[n - 1];
		for(int m = 1; m < n; m ++)
			(ans += (jc[n - m] * Ksm(n, m - 1) % P) * a[m] % P) %= P;
		ans = ans * Ksm(n, P - 2) % P;
		for(int i = 1; i <= n; i ++)
			ans = ans * inv[d[i]] % P;
		cout << ans << "\n";
		return 0;
	}
}
signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0), cout.tie(0);
	int T = 1;
	while(T --) azus::main();
	return 0;
}

标签:int,back,poly,++,push,数树题,tmp2
From: https://www.cnblogs.com/AzusidNya/p/18235988

相关文章