传送门:https://ac.nowcoder.com/acm/contest/203/B
题意
给一棵树,对于每个节点,输出与它相连的所有子集个数。
思路
树,节点,计数-树形dp。
设f[i]为以i为根节点的字树中,包括i的子集个数,所以对于根节点,答案为f[root]。
状态转移方程:
$$f[u]=\prod(f[v]+1)\;\;v为与u相连儿子$$
为什么+1,因为自己是子集的一部分。
上面说到,只有对于根节点,ans才为f[root],但是不是根节点呢?
最简单的方法是,把每个点当作root,dfs一遍。但是数据范围不允许。
$但是我们看到对于以u为根节点的叶子节点,只有父亲节点那边的子集个数没有统计。
所以我们再dfs’一遍,首先是没有父亲,所以ans=f[u],然后利用父亲更新节点。
如果更新呢?
f[u]已经统计了所有子节点,没有统计父亲节点,所以需要乘上父亲节点那边的贡献。
贡献为$\frac{ans[fa]}{f[u]+1}$,所以$ans[u]=(\frac{ans[fa]}{f[u]+1}+1)*f[u]$
对于$(f[u]+1) \% mod=0$,因为0没有逆元,所以要特别统计(最大坑点)。 最简单的就是以它为根节点dfs一遍,ans=f[u]。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| ##include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7; const int N = 1e6 + 10;
ll quick_pow(ll a, ll b) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans % mod; }
vector<int> g[N]; ll ans[N], f[N];
void dfs1(int u, int fa) { f[u] = 1; for(auto v : g[u]) { if(v == fa) continue; dfs1(v, u); f[u] = f[u] * (f[v] + 1) % mod; } }
void dfs2(int u, int fa) { if(fa != -1) { if((f[u] + 1) % mod == 0) { dfs1(u, -1); ans[u] = f[u]; } else ans[u] = (ans[fa] * (quick_pow(1 + f[u], mod - 2) % mod) % mod + 1) * f[u] % mod; } else ans[u] = f[u]; for(auto v : g[u]) { if(v == fa) continue; dfs2(v, u); } }
void solve() { int n; cin >> n; for(int i = 1;i < n; i++) { int u, v; cin >> u >> v; g[u].eb(v); g[v].eb(u); } dfs1(1, -1); dfs2(1, -1); for(int i = 1;i <= n; i++) cout << ans[i] << endl; }
signed main() { solve(); }
|
本文作者:jujimeizuo
本文地址: https://blog.jujimeizuo.cn/2021/03/25/2018-nowcoder-guoqing-day3-b-tree/
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0 协议。转载请注明出处!