From 6a8653ce104cbc428c0e07fa0c1c304c8e8cbbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Thu, 18 Jan 2024 09:09:31 +0800 Subject: [PATCH] 'commit' --- TangDou/Topic/HuanGenDp/SubTree.cpp | 72 +++++++++++++------------- TangDou/Topic/HuanGenDp/SubTree.drawio | 1 + TangDou/Topic/【换根DP】专题.md | 68 ++++++++++-------------- 3 files changed, 66 insertions(+), 75 deletions(-) create mode 100644 TangDou/Topic/HuanGenDp/SubTree.drawio diff --git a/TangDou/Topic/HuanGenDp/SubTree.cpp b/TangDou/Topic/HuanGenDp/SubTree.cpp index 8144447..ea3cee7 100644 --- a/TangDou/Topic/HuanGenDp/SubTree.cpp +++ b/TangDou/Topic/HuanGenDp/SubTree.cpp @@ -1,59 +1,61 @@ #include using namespace std; +const int N = 1e5 + 10, M = N << 1; #define int long long #define endl "\n" -const int N = 100010; +// 链式前向星 +int e[M], h[N], idx, w[M], ne[M]; +void add(int a, int b, int c = 0) { + e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; +} +int f[N], g[N], son[N], ans[N]; int n, mod; +vector val[N], pre[N], suc[N]; -vector s[N]; -int f[N], g[N]; - -void dfs1(int x, int fa) { - for (int v : s[x]) { +void dfs1(int u, int fa) { + int tmp = 1; + val[u].push_back(1); + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; if (v == fa) continue; - dfs1(v, x); - f[x] = f[x] * (f[v] + 1) % mod; + dfs1(v, u); + son[u]++; + val[u].push_back(f[v] + 1); + tmp = tmp * (f[v] + 1) % mod; } + f[u] = tmp; } -void dfs2(int x, int fa) { - int t = g[x]; - for (int v : s[x]) { - if (v == fa) continue; - g[v] = g[v] * t % mod; - } - - t = 1; - for (int v : s[x]) { - if (v == fa) continue; - g[v] = g[v] * t % mod, t = t * (f[v] + 1) % mod; - } - - t = 1; - reverse(s[x].begin(), s[x].end()); - for (int v : s[x]) { - if (v == fa) continue; - g[v] = g[v] * t % mod, t = t * (f[v] + 1) % mod; - } - - for (int v : s[x]) { +void dfs2(int u, int fa) { + if (u == 1) g[u] = 1; + ans[u] = f[u] * g[u] % mod; + pre[u].resize(son[u] + 2); + suc[u].resize(son[u] + 2); + pre[u][0] = suc[u][son[u] + 1] = 1; + for (int i = 1; i <= son[u]; i++) pre[u][i] = (ll)pre[u][i - 1] * val[u][i] % mod; + for (int i = son[u]; i >= 1; i--) suc[u][i] = (ll)suc[u][i + 1] * val[u][i] % mod; + int m = 0; + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; if (v == fa) continue; - g[v]++; - dfs2(v, x); + m++; + g[v] = g[u] * pre[u][m - 1] % mod * suc[u][m + 1] % mod; + g[v] = (g[v] + 1) % mod; + dfs2(v, u); } } signed main() { + // 初始化链式前向星 + memset(h, -1, sizeof h); cin >> n >> mod; - for (int i = 1; i <= n; i++) f[i] = g[i] = 1; for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; - s[a].push_back(b), s[b].push_back(a); + add(a, b), add(b, a); } - dfs1(1, 0); dfs2(1, 0); - for (int i = 1; i <= n; ++i) cout << f[i] * g[i] % mod << endl; + for (int i = 1; i <= n; i++) cout << ans[i] << endl; } \ No newline at end of file diff --git a/TangDou/Topic/HuanGenDp/SubTree.drawio b/TangDou/Topic/HuanGenDp/SubTree.drawio new file mode 100644 index 0000000..ad26694 --- /dev/null +++ b/TangDou/Topic/HuanGenDp/SubTree.drawio @@ -0,0 +1 @@ +7Vptb9s2EP41BNoBKyRSL9RHSbZXrBuwIh+2fhrUWLGVylamyI6zX7878kRTluw4iBu7wZIgJo93vLvnXigqYSJdbH6ps7v579U0Lxl3phsmRoxzNwgFfCDlUVOiMNKEWV1MiWlLuCr+zYnoEHVVTPP7DmNTVWVT3HWJ19VymV83HVpW19VDl+2mKrta77JZ3iNcXWdln/pnMW3mRHWDaLvwMS9mc1IteagXFlnLTJ7cz7Np9WCRxJiJtK6qRo8WmzQvEbwWFy032bNqDKvzZXOMQOV+WzTR7d8fP99Ok39Wv34Wnz797HpkXPPYepxPAQCaLqslfCR1tVpOc9zHgVlVN/NqVi2z8requgOiC8TbvGkeKXzZqqmANG8WJa3mm6L5C8U/+DT7Yq2MNrSzmjy2k2VTP1pCOP1ir23F1KyVu6mWDRnCXZqnVVnVyj3hSPwGunYcvd0LKJHuq1V9nR9CkRIzq2d5c4CPm7BDveTVIge7Qa7Oy6wp1l07MkrcmeHbxhYGFN7nhNr/8UL9gftPRFvN/sjrAkDK68tPAXHWFND7rrNyRZpW/ZwoS+itmAoP86LJr+4y5fcDtPduoLP7O91wb4oNJsyTqDuTiaOiU5Tl/mis87rJN4fj0cePBHhEInTawO56/rDt3R6R5lbXbmknRzzq4XtZBXZ5vZQfWUjeWQvJ6cX1UnrpBYfMP2vI3IsN2RuszuCcoea9Y249EPyTnXP3TV19yy3gv0rf852BgLzCCej63RNQRGc+AV3+f929vO7EkXUXnrXFih8v1D/ODePYFJDnTAHRb70D9X+6K8aeRrqv8Z6gwXruhTVYrwf56wE+UV9P5v+LAA+cDt4+PzPe/hvH2/UvDPDgjQNuXlJcCuDhGwdchO5lAS7fOOCef2GAu0MpHpSgNvkKgxkObpifrJg/YmOfxWMWxWzssSRiMmpZQbXhNuK1oSB7wmJ/pUaSJbBDwGIHNxlPWJIymeLmME0C4ok8aylgMoaflWJKlbzPpFTcAYsmLBLW1hFqi1w2lmhiwhVPqJitHeMRi72hJeWkhA1DdFVqHtDrKRUjpcsotVUEyh/Y2UXbSMpBe2TI5ISNBZMOk3wAof2UfhAk2gc6wD6MRrhfuBMW8CtKWSwJesRHIQbIwk4EpocAoju27/v8MohJdCqeWPkBPFJRQgxk4lqoBogY4IA8KY4xlMCjxQFe5zjtPRgwN/QAeCC4/hZwrStOlYNgRkK+g7pO/jnKVGO8shPdkWgeBJGkvHYQ943HFywpPOoP2CeVNemAfcckBAKn8YJogbKwtY8r00dqYAGHSyM1ECwRlp+RipZoi1jSEgj2lGLSgUO69AkdXcsq9cE7zCeVNDIwZb52X1jna/d0hW5yirJ+7b5mYSsA+esDyL8jgPzMnZGkRqoQOO1MBWWQpJTeJjBPO7EApbHdpw4ExQTA6gt7YdtFq1uSO43vWViZgCiotWFkj32qaVN1Zx+1A8dqzUNtbtcdW1eERhJiE8Uj0IBOi48U5aD4rlJjoQUCtNEo6vTWXTDtnbW4llLHmxyjI8TjDp3xsGHQngdjjMJAcA+40++cRkXfVF+dGT75LpV27NvyoC5zYhk0tIVht4GkFMqYoyO4c4xs23CHeEAQvAczSu9Dhhl7WgyPPZh0YlP7YmLgKHmiuZ2qSnYTzWoAyZA7xir+6lbx/VYN2OljCumAmoyFKRQ1VZnSBwd95FMtRDrBnG0/pBNCtg9/iXokCpETm2eEEQeXuGMeNvFC4LzrgPj+p3cd89/3bmhwPWq6V7HuH4bo9bd9/SJSVhazJUzL/AZ3wKtWcZ2VMZEXxXRa7rv5dd+m7971TnB38wO/c3cbeN1pWOy7Wyi+2+Vt4Lr8vMQ0CW4eJQNMHewFqikk6hoEv5PkO4T5GpDHPyScKNBCDL3wxq+Wj4x3T5ANvVcn/WwQwcBNnj8/GWC6/edItWb9i6kY/wc= \ No newline at end of file diff --git a/TangDou/Topic/【换根DP】专题.md b/TangDou/Topic/【换根DP】专题.md index 4aec604..870633a 100644 --- a/TangDou/Topic/【换根DP】专题.md +++ b/TangDou/Topic/【换根DP】专题.md @@ -1275,50 +1275,39 @@ signed main() { 对于每一个节点 $i$,求强制把第 $i$ 节点染成黑色的情况下,所有的黑色节点组成一个联通块的染色方案数,答案对 $M$ 取模。 **分析** -树上求方案数,而且要求每一个顶点对应的方案数,还是在 $AT$ 的 $DP$ 列表里的,考虑进行换根$DP$。 +题目要求我们求出对每一个点强制染黑的情形的答案,故考虑采用换根 $DP$。 +先计算钦定 $1$ 号点为根并染黑的方案数。 -**求解** -第一步,先求出以第 $i$ 个点为根的子树中,根节点是黑色的黑连通块数量,就是一个普通的树上 $DP$,状态从子节点转移,结果储存在 -$dp[i]$ 里。 +**状态转移方程** +设 $f_u$ 表示将 $u$ 号点染黑,且其子树内黑点构成连通块的方案数。易见,对于其每一个子节点 +$v$,都有染黑和不染黑两种选择:染黑则方案数为 $f_v$ ;不染黑则 $v$ 的整棵子树都只能为白,方案数为 $1$。故 +$$\large f_u= \prod_{v \in son_u} (f_v+1) $$ +> **解释**: +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202401180842993.png) -```cpp {.line-numbers} -void DP(int x,int fa){ - for(int i:s[x]){ - if(i!=fa){ - DP(i,x); - dp[x]=dp[x]*(dp[i]+1)%m;//子树全白也是一种情况 - } - } -} -``` -第二步,求出第 $i$ 个节点通过它的父亲节点,与此节点的兄弟节点们及其子孙节点和此节点的祖先节点们,所能构成的黑连通块的总数,结果存在 $pd[i]$ 里。 -这里有换根的思想。首先它的父亲肯定是黑的,这时把它的父亲节点视作根节点,把它自己及其子树删去,和第一步同样的方法求解。 +**初始化** +当 $u$ 为叶子时,显然 $f_u =1$,与初始化要求相同。 -```cpp {.line-numbers} -void PD(int x,int fa){ - Int t=pd[x]; - for(int i:s[x])//它的祖先们转移过来的 - if(i!=fa) - pd[i]=pd[i]*t%m; - t=1; - for(int i:s[x])//它左侧的兄弟们转移过来的 - if(i!=fa) - pd[i]=pd[i]*t%m,t=t*(dp[i]+1)%m; - t=1; - std::reverse(s[x].begin(),s[x].end()); - for(int i:s[x])//它右侧的兄弟们转移过来的 - if(i!=fa) - pd[i]=pd[i]*t%m,t=t*(dp[i]+1)%m; - for(int i:s[x])if(i!=fa){ - ++pd[i]; - PD(i,x); - } -} -``` -显而易见的,步骤一和步骤二中的答案互不相干,可以让节点上方随意排布,节点下方随意排布。根据乘法原理,答案即为 -$dp[i] \times pd[i]$ 。 +> 解释:**对于每一个点,求强制给该点染上黑色时,整棵树上的黑点构成一个连通块的染色方案数。** 这是题目的要求!当一个子树中只有一个点时,还要强制给该点染上黑色,那就只有一种方案。 +现在考虑换根。不难理解,如果$v \in son[u]$,换根时消去 $v$ 对 $u$ 的贡献,再将 $u$ 的贡献乘到 $v$ 上。 + +- 消去 $v$ 对 $u$ 的贡献: + + $\large f'_{u}=\frac{f_{u}}{f_v+1}$ + +- 将 $f'_u$ 的贡献乘到 $v$ 上: + + $\large f'_v=f_v \times (f'_u+1)$ + + + + 不过出现了一个小问题:**模数不保证是质数**,所以不能用直接乘逆元的方式来取模。 + + 这里,对于每一个点 $u$,处理出 $f_{v∈son[u]}+1$ 的 **前缀积** 和 **后缀积**,即可解决求出消去一个子树贡献后的答案的问题。 + + #### [$AcWing$ $1148$ 秘密的牛奶运输](https://www.cnblogs.com/littlehb/p/16054005.html) @@ -1334,4 +1323,3 @@ https://www.cnblogs.com/DongPD/p/17498336.html -