[题解][YZOJ7031] 计树
复习?
题目大意#
给出一个长度为 的排列,要求构建一棵 个节点的有标号树,满足若存在边 则存在边 。
求构建树的方案数。
,模数为 。
解题思路#
首先对于这种 和 相对应的题目,不妨先把置换环提出来,然后考虑题目的条件实际是要干什么。
于是不难意识到,若 连边,则要在两者所在的环上依次连边,那么现在需要考虑是否所有的连边都合法。
记 所在的环长度分别为 ,根据手玩和置换的一些特性,可以发现:
- 。
- 只能和唯一一个大小为 连边。
hint:非法连边的情况都是出现环。
此时环与环之间的关系,实际上也就是一个树的关系,大小为 的环,其父亲一定是一个大小为 的因数的环。
故计数时,不妨枚举 ,然后考虑其与父亲的连边方案,那么对于某一个 ,其方案应对是:
首先考虑 这一层内的环相互连边,枚举连出来 棵树,第一部分即 个点,形成 棵树的森林的方案树
(考虑一个虚拟根节点,然后 prufer 序列计算)。
然后要考虑的就是,某两个环之间连边,方案数是多少,不难发现是两个环中较小的那个的大小。
第二部分即同层的环之间的连的边的选取方案数。
第三部分是 棵树的根连向较小的层连边的边的选取的方案数 ()。
做到这里,我们其实还有一个问题没有考虑,一棵树,除了没有环,还应当保证连通。
如果最小的环大小为 ,那么只要把这些点连成一棵无根树即可。
如果最小的环大小为 ,那么其实要连成一棵有根树,为什么呢,因为仅仅按照环间连边的话,最后还是会有两棵树,所以要选择一个大小为 的环进行环内连边,使其连通。
看到这里,不知道有没有意识到之前都没有提及环内连边,原因是 的环,出现环内连边后,最终将因为无法和小于等于自己的其他环连边 (会成环),而最终无法连通,于是没有合法方案。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具