[题解][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\),其方案应对是:
首先考虑 \(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;
}