zoukankan      html  css  js  c++  java
  • LOJ#2983. 「WC2019」数树

    传送门

    抄题解
    (Task0),随便做一下,设 (cnt) 为相同的边的个数,输出 (y^{n-cnt})
    (Task1),给定其中一棵树
    设初始答案为 (y^n),首先可以发现,每有一条边和给定的树相同就会使得答案除去 (y)
    那么可以利用矩阵树定理,已经有的边权值为 (y^{-1}),其它的连成完全图,权值为 (1)
    求解行列式之后乘上 (y^n) 即可,(O(n^3))
    第一种正解 (orz~laofu) 即可 不会
    第二种正解

    一个小trick
    对于求恰好 (k) 个的方案数,并且贡献为 (x^k) 的形式
    可以利用二项式定理展开
    (x^k=sum_{i=0}^{k}inom{k}{i}(x-1)^k)
    含义为枚举所有的集合 (E),如果 (E) 是答案的子集,那么贡献为 ((x-1)^{|E|}),否则为 (0)
    所有子集 (E) 的贡献之和恰好是 (x^k),这样可以省去容斥

    那么可以直接算 (y^{-1}-1) 在每一种两棵树都包含的边集 (|E|) 的贡献
    下面的 (y) 默认为 (y^{-1}-1)
    对于一种边集 (|E|),假设它把图分成了 (m) 个连通块,每个连通块大小为 (a_i)
    运用 (prufer) 的知识,不难得到其生成树的方案数为 (n^{m-2}prod_{i=1}^{m}a_i)
    直观上来看这个东西,只能得到一个 (O(n^2))(dp)(f_{i,j}) 表示 (i) 所在连通块,大小为 (j)
    然后考虑 (prod_{i=1}^{m}a_i) 的组合意义,即从每一个连通块选出一个点的方案数
    那么就可以把上面的 (dp) 改成 (f_{i,0/1}) 表示 (i) 所在连通块是否选择了一个点
    (O(n)) 树形 (dp) 即可
    (Task2),同样的利用那一个trick
    (f_i) 表示钦定 (i) 条边相同的方案数
    那么 (ans=sum_{i=0}^{n-1}f_iy^i)
    考虑计算 (f_i),还是直接利用 (prufer),容易得到

    [f_x=sum_{sum_{i=1}^{n-x}a_i=n}frac{n!prod_{i=1}^{n-x}frac{a_i^{a_i-2}}{a_i!}}{(n-x)!} imes (n^{n-x-2}prod_{i=1}^{n-x}a_i)^2 ]

    (含义参考:分配标号,连通块之间无序)

    [f_x=sum_{sum_{i=1}^{n-x}a_i=n}frac{n!n^{2(n-x-2)}prod_{i=1}^{n-x}frac{a_i^{a_i}}{a_i!}}{m!} ]

    根据这个式子不难得到一个 (O(n^3))(dp),枚举划分
    继续推导,直接代到答案里面,可以得到

    [ans=sum_{i=1}^{n}frac{y^{n-i}n^{2(i-2)}}{i!}sum_{sum_{j=1}^{i}a_j=n}prod_{j=1}^{i}frac{a_j^{a_j}}{a_j!} ]

    [=frac{y^n}{n^4}sum_{i=1}^{n}frac{n^{2i}}{y^ii!}sum_{sum_{j=1}^{i}a_j=n}prod_{j=1}^{i}frac{a_j^{a_j}}{a_j!} ]

    (sum_{sum_{j=1}^{i}a_j=n}prod_{j=1}^{i}frac{a_j^{a_j}}{a_j!}) 可以写成生成函数的形式
    ([x^n](sum_{j=1}^{n}frac{j^j}{j!}x^j)^i)
    代入

    [ans=frac{y^n}{n^4}sum_{i=1}^{n}frac{n^{2i}}{y^ii!}[x^n](sum_{j=1}^{n}frac{j^j}{j!}x^j)^i ]

    [=frac{y^n}{n^4}[x^n]sum_{i=1}^{n}frac{(frac{n^2}{y}sum_{j=1}^{n}frac{j^j}{j!}x^j)^i}{i!} ]

    (F=frac{n^2}{y}sum_{j=1}^{n}frac{j^j}{j!}x^j)
    忽略与 (i) 无关项,就是

    [[x^n]sum_{i=1}^{n}frac{F^i}{i!} ]

    这个东西直接写成无穷项的形式不会影响答案,不难发现就是

    [[x^n]e^{F} ]

    多项式 (exp) 即可
    (O(nlogn))

    # include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int maxn(1e5 + 5);
    const int mod(998244353);
    
    inline void Inc(int &x, int y) {
        x = x + y >= mod ? x + y - mod : x + y;
    }
    
    inline void Dec(int &x, int y) {
        x = x - y < 0 ? x - y + mod : x - y;
    }
    
    inline int Add(int x, int y) {
        return x + y >= mod ? x + y - mod : x + y;
    }
    
    inline int Sub(int x, int y) {
        return x - y < 0 ? x - y + mod : x - y;
    }
    
    inline int Pow(ll x, int y) {
    	ll ret = 1;
    	for (; y; y >>= 1, x = x * x % mod)
    		if (y & 1) ret = ret * x % mod;
    	return ret;
    }
    
    int n, y, op;
    
    namespace Task0 {
    	map < pair <int, int> , int > vis;
    
    	inline void Solve() {
    		int i, u, v, cnt = 0;
    		for (i = 1; i < n; ++i) {
    			scanf("%d%d", &u, &v);
    			if (u > v) swap(u, v);
    			vis[make_pair(u, v)] = 1;
    		}
    		for (i = 1; i < n; ++i) {
    			scanf("%d%d", &u, &v);
    			if (u > v) swap(u, v);
    			if (vis.count(make_pair(u, v))) ++cnt;
    		}
    		printf("%d
    ", Pow(y, n - cnt));
    	}
    }
    
    namespace Task1 {
    	int first[maxn], cnt, f0[maxn], f1[maxn];
    
    	struct Edge {
    		int to, next;
    	} edge[maxn << 1];
    
    	inline void AddEdge(int u, int v) {
    		edge[cnt] = (Edge){v, first[u]}, first[u] = cnt++;
    		edge[cnt] = (Edge){u, first[v]}, first[v] = cnt++;
    	}
    
    	void Dfs(int u, int ff) {
    		int e, v, tmp;
    		f1[u] = f0[u] = 1;
    		for (e = first[u]; ~e; e = edge[e].next)
    			if ((v = edge[e].to) ^ ff) {
    				Dfs(v, u), tmp = (ll)f1[u] * f1[v] % mod * n % mod;
    				Inc(tmp, ((ll)f1[u] * f0[v] % mod + (ll)f0[u] * f1[v] % mod) * y % mod);
    				f1[u] = tmp, tmp = (ll)f0[u] * f0[v] % mod * y % mod;
    				Inc(tmp, (ll)f1[v] * f0[u] % mod * n % mod);
    				f0[u] = tmp;
    			}
    	}
    
    	inline void Solve() {
    		int i, u, v, w = Pow(y, n), ans;
    		y = Pow(y, mod - 2), Dec(y, 1);
    		memset(first, -1, sizeof(first));
    		for (i = 1; i < n; ++i) scanf("%d%d", &u, &v), AddEdge(u, v);
    		Dfs(1, 0);
    		ans = (ll)f1[1] * w % mod * Pow(n, mod - 2) % mod;
    		printf("%d
    ", ans);
    	}
    }
    
    namespace Task2 {
    	int f[maxn << 2], g[maxn << 2], ans, fac[maxn], inv[maxn];
    	int w[2][maxn << 2], r[maxn << 2], l, deg;
    
    	inline void Init(int len) {
    		int i, x, y;
    		for (l = 0, deg = 1; deg < len; deg <<= 1) ++l;
    		for (i = 0; i < deg; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    		x = Pow(3, (mod - 1) / deg), y = Pow(x, mod - 2), w[0][0] = w[1][0] = 1;
    		for (i = 1; i < deg; ++i) w[0][i] = (ll)w[0][i - 1] * x % mod, w[1][i] = (ll)w[1][i - 1] * y % mod;
    	}
    
    	inline void DFT(int *p, int opt) {
    		int i, j, k, t, wn, x, y;
    		for (i = 0; i < deg; ++i) if (r[i] < i) swap(p[r[i]], p[i]);
    		for (i = 1; i < deg; i <<= 1)
    			for (t = i << 1, j = 0; j < deg; j += t)
    				for (k = 0; k < i; ++k) {
    					wn = w[opt == -1][deg / t * k];
    					x = p[j + k], y = (ll)p[j + k + i] * wn % mod;
    					p[j + k] = Add(x, y), p[j + k + i] = Sub(x, y);
    				}
    		if (opt == -1) for (i = 0, wn = Pow(deg, mod - 2); i < deg; ++i) p[i] = (ll)p[i] * wn % mod;
    	}
    
    	void Inv(int *p, int *q, int len) {
    		if (len == 1) {
    			q[0] = Pow(p[0], mod - 2);
    			return;
    		}
    		Inv(p, q, len >> 1);
    		static int a[maxn << 2], b[maxn << 2];
    		int tmp = len << 1, i;
    		Init(tmp);
    		for (i = 0; i < tmp; ++i) a[i] = b[i] = 0;
    		for (i = 0; i < len; ++i) a[i] = p[i], b[i] = q[i];
    		DFT(a, 1), DFT(b, 1);
    		for (i = 0; i < tmp; ++i) a[i] = (ll)a[i] * b[i] % mod * b[i] % mod;
    		DFT(a, -1);
    		for (i = 0; i < len; ++i) q[i] = Sub(Add(q[i], q[i]), a[i]);
    	}
    
    	inline void Calc(int *p, int *q, int len) {
    		int i;
    		for (i = len - 2; ~i; --i) q[i + 1] = (ll)p[i] * Pow(i + 1, mod - 2) % mod;
    		q[0] = 0;
    	}
    
    	inline void ICalc(int *p, int *q, int len) {
    		int i;
    		for (i = len - 2; ~i; --i) q[i] = (ll)p[i + 1] * (i + 1) % mod;
    		q[len - 1] = 0;
    	}
    
    	inline void Ln(int *p, int *q, int len) {
    		static int a[maxn << 2], b[maxn << 2];
    		int tmp = len << 1, i;
    		for (i = 0; i < tmp; ++i) a[i] = b[i] = 0;
    		ICalc(p, a, len), Inv(p, b, len);
    		DFT(a, 1), DFT(b, 1);
    		for (i = 0; i < tmp; ++i) a[i] = (ll)a[i] * b[i] % mod;
    		DFT(a, -1), Calc(a, q, len);
    	}
    
    	void Exp(int *p, int *q, int len) {
    		if (len == 1) {
    			q[0] = 1;
    			return;
    		}
    		Exp(p, q, len >> 1);
    		static int a[maxn << 2], b[maxn << 2];
    		int tmp = len << 1, i;
    		Init(tmp);
    		for (i = 0; i < tmp; ++i) a[i] = b[i] = 0;
    		Ln(q, a, len);
    		for (i = 0; i < len; ++i) a[i] = Sub(p[i], a[i]), b[i] = q[i];
    		Inc(a[0], 1), DFT(a, 1), DFT(b, 1);
    		for (i = 0; i < tmp; ++i) a[i] = (ll)a[i] * b[i] % mod;
    		DFT(a, -1);
    		for (i = 0; i < len; ++i) q[i] = a[i];
    	}
    
    	inline void Solve() {
    		int i, j, k, w = Pow(y, n), len;
    		if (n == 1) {
    			printf("%d
    ", y);
    			return;
    		}
    		if (y == 1) {
    			printf("%d
    ", Pow(n, (n - 2) << 1));
    			return;
    		}
    		y = Pow(y, mod - 2), Dec(y, 1), fac[0] = inv[0] = inv[1] = 1;
    		for (i = 1; i <= n; ++i) fac[i] = (ll)fac[i - 1] * i % mod;
    		for (i = 2; i <= n; ++i) inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
    		for (i = 2; i <= n; ++i) inv[i] = (ll)inv[i] * inv[i - 1] % mod;
    		for (i = 1; i <= n; ++i) f[i] = (ll)Pow(i, i) * inv[i] % mod * Pow(y, mod - 2) % mod * n % mod * n % mod;
    		for (len = 1; len <= n; len <<= 1);
    		Exp(f, g, len);
    		ans = (ll)g[n] * w % mod * fac[n] % mod * Pow(y, n) % mod * Pow(Pow(n, mod - 2), 4) % mod;
    		printf("%d
    ", ans);
    	}
    }
    
    int main() {
    	freopen("tree.in", "r", stdin);
    	freopen("tree.out", "w", stdout);
    	scanf("%d%d%d", &n, &y, &op);
    	if (!op) Task0 :: Solve();
    	else if (op == 1) Task1 :: Solve();
    	else Task2 :: Solve();
    	return 0;
    }
    
  • 相关阅读:
    SqlServer数据库同步方案详解(包括跨网段)
    makefile 和shell文件相互调用
    处理百万级以上的数据处理
    makefile Template(添加多个lib)
    Linux下如何删除非空目录
    makefile Template
    g++ 编译和链接
    gcc include路径
    C++ XML解析之TinyXML篇
    利用Lucene.net搭建站内搜索(2)分词技术
  • 原文地址:https://www.cnblogs.com/cjoieryl/p/10347235.html
Copyright © 2011-2022 走看看