Loading

[题解][YZOJ7031] 计树

复习?

题目大意

给出一个长度为 \(n\) 的排列,要求构建一棵 \(n\) 个节点的有标号树,满足若存在边 \((i,j)\) 则存在边 \((p_i,p_j)\)

求构建树的方案数。

\(n\le3\times10^5\),模数为 \(998244353\)

解题思路

首先对于这种 \(i\)\(p_i\) 相对应的题目,不妨先把置换环提出来,然后考虑题目的条件实际是要干什么。

于是不难意识到,若 \(x,y\) 连边,则要在两者所在的环上依次连边,那么现在需要考虑是否所有的连边都合法。

\(x,y\) 所在的环长度分别为 \(n,m\ (n\ge m)\),根据手玩和置换的一些特性,可以发现:

  • \(m|n\)
  • \(n\) 只能和唯一一个大小为 \(m\) 连边。

hint:非法连边的情况都是出现环。

此时环与环之间的关系,实际上也就是一个树的关系,大小为 \(n\) 的环,其父亲一定是一个大小为 \(n\) 的因数的环。

故计数时,不妨枚举 \(n\),然后考虑其与父亲的连边方案,那么对于某一个 \(n\),其方案应对是:

\[\sum_{i=1}^{cnt_n}\binom{cnt_n-1}{i-1}\cdot n^{cnt_n-i}\times cnt_n^{cnt_n-i}\times val^i \]

首先考虑 \(n\) 这一层内的环相互连边,枚举连出来 \(i\) 棵树,第一部分即 \(cnt_n\) 个点,形成 \(i\) 棵树的森林的方案树

(考虑一个虚拟根节点,然后 prufer 序列计算)。

然后要考虑的就是,某两个环之间连边,方案数是多少,不难发现是两个环中较小的那个的大小。

第二部分即同层的环之间的连的边的选取方案数。

第三部分是 \(i\) 棵树的根连向较小的层连边的边的选取的方案数 (\(val=\sum_{m|n}m\cdot cnt_m\))。

做到这里,我们其实还有一个问题没有考虑,一棵树,除了没有环,还应当保证连通。

如果最小的环大小为 \(1\),那么只要把这些点连成一棵无根树即可。

如果最小的环大小为 \(2\),那么其实要连成一棵有根树,为什么呢,因为仅仅按照环间连边的话,最后还是会有两棵树,所以要选择一个大小为 \(2\) 的环进行环内连边,使其连通。

看到这里,不知道有没有意识到之前都没有提及环内连边,原因是\(>2\) 的环,出现环内连边后,最终将因为无法和小于等于自己的其他环连边 (会成环),而最终无法连通,于是没有合法方案。

int main(){
	read(n), prep(n);
	lfor(i, 1, n) read(p[i]), G[p[i]].pb(i), G[i].pb(p[i]);
	lfor(i, 1, n) if(!vis[i]) dcnt = 0, dfs(i), ++c[dcnt];
	rfor(i, n, 1) if(c[i]) a[++m] = {i, c[i]};
	int Ans = 1;
	lfor(i, 1, m - 1){
		int sqr = sqrt(a[i].fi), val = mod - 1LL * a[i].fi * c[a[i].fi] % mod;
		lfor(j, 1, sqr) if(a[i].fi % j == 0){
			MOD(val += 1LL * j * c[j] % mod - mod);
			MOD(val += 1LL * (a[i].fi / j) * c[a[i].fi / j] % mod - mod); 
		}
		if(sqr * sqr == a[i].fi) MOD(val -= 1LL * sqr * c[sqr] % mod - mod);
		int res = 0;
		lfor(j, 1, a[i].se){
			int sum = 1LL * C(a[i].se - 1, j - 1) * qpow(a[i].se, a[i].se - j) % mod;
			sum = 1LL * sum * qpow(val, j) % mod;
			sum = 1LL * sum * qpow(a[i].fi, a[i].se - j) % mod;
			MOD(res += sum - mod);
		}
		Ans = 1LL * Ans * res % mod;
	}
	if(a[m].fi == 1){
		if(a[m].se > 1) Ans = 1LL * Ans * qpow(a[m].se, a[m].se - 2) % mod;
	}else if(a[m].fi == 2){
		Ans = 1LL * Ans * qpow(a[m].se, a[m].se - 1) % mod * qpow(2, a[m].se - 1) % mod;
	}else Ans = 0;
	cout << Ans << endl;
	return 0;
}
posted @ 2022-05-13 10:29  IrisT  阅读(89)  评论(0编辑  收藏  举报