NC19782 Tree
题目
题目描述
修修去年种下了一棵树,现在它已经有n个结点了。
修修非常擅长数数,他很快就数出了包含每个点的连通点集的数量。
澜澜也想知道答案,但他不会数数,于是他把问题交给了你。
输入描述
第一行一个整数 ,接下来n-1行每行两个整数 表示一条边 。
输出描述
输出n行,每行一个非负整数。第i行表示包含第i个点的连通点集的数量对 取模的结果。
示例1
输入
6 1 2 1 3 2 4 4 5 4 6
输出
12 15 7 16 9 9
题解
知识点:树形dp,计数dp。
一道树形dp,注意到要求每个点的贡献,因此需要二次扫描+换根。
第一次先随便选个点,这里考虑以 为根开始dp。设 表示 为根的子树中过 的连通点集数量,转移方程为:
表示合并以子节点 为根的子树的方案数,加一是不选 这个子树的一种可能,乘法原理合并。
第二次,在得到了各个子树答案后,可以顺推完整答案。设 表示过 的连通点集数量,转移方程为:
表示从父节点的答案 删去选或不选 这一支路选的答案 ,再加上不选父节点 的答案一种,与 支路的答案进行乘法原理合并,最终得到 的完整答案 ,其中需要求 的逆元。
但有个大坑, 时是没有逆元的,这时候只能重新求一遍 。
如果重新开始求,把 作为根节点再求一次 ,这样复杂可能会退化到枚举根的程度。
因此,考虑设 ,于是 , 可以在每次求 前先求出。如果 有逆元 ,则 ;如果没有,则 ,其中 只需要求一遍 。注意初始时 ,因为此时 。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> #define ll long long using namespace std; const int mod = 1e9 + 7; struct Graph { int n; struct edge { int to, nxt; }; vector<edge> e; vector<int> h; explicit Graph(int _n) :n(_n), h(_n + 1, -1) {} void add(int u, int v) {///加边 e.push_back(edge{ v,h[u] });///边结束节点,边出发节点的上一条边在e中下标,边权 h[u] = e.size() - 1;///上一条边的下标 } }; Graph g(1000007); ll dp[1000007], f[1000007]; ll qpow(ll a, ll k) { ll ans = 1; while (k) { if (k & 1) ans = ans * a % mod; k >>= 1; a = a * a % mod; } return ans; }///求逆元,但得到0时代表没有逆元,因此要重新求 void dfs1(int u, int fa) { dp[u] = 1; for (int i = g.h[u];~i;i = g.e[i].nxt) { int v = g.e[i].to; if (v == fa) continue; dfs1(v, u); dp[u] = dp[u] * (dp[v] + 1) % mod; } }///处理出子树中过根节点的答案dp[u] void dfs2(int u, int fa) { for (int i = g.h[u];~i;i = g.e[i].nxt) { int v = g.e[i].to; if (v == fa) continue; int inv = qpow(dp[v] + 1, mod - 2); if (inv) f[v] = dp[v] * (f[u] * inv % mod + 1) % mod;///inv可以用 else { int tmp = 1; for (int j = g.h[u];~j;j = g.e[j].nxt) { int vt = g.e[j].to; if (vt == fa || vt == v) continue; tmp = tmp * (dp[vt] + 1) % mod; } f[v] = dp[v] * (tmp + 1) % mod; }///不能用就重新算一下f[u]/(dp[v]+1) dfs2(v, u); } }///通过上一次处理的答案dp[u],求出完整答案f[v] = dp[v] * (f[u]/(dp[v]+1) + 1) ///表示自己子树的答案与父节点除了自己的答案加上不选父节点的答案相互匹配 int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i < n;i++) { int u, v; cin >> u >> v; g.add(u, v); g.add(v, u); } dfs1(1, 0); f[1] = dp[1];///初始化 dfs2(1, 0); for (int i = 1;i <= n;i++) cout << f[i] << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/16622885.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧