当前位置: 首页 > news >正文

(树形 dp、数学)AT_dp_v Subtree 题解

题意

给一棵 nnn 个节点的树,对每一个节点染成黑色或白色。

对于每一个节点,求强制把这个节点染成黑色的情况下,所有的黑色节点组成一个联通块的染色方案数,答案对 ppp 取模。

1≤n≤1051\le n\le 10^51n105.

思路

由于钦定选哪一个点强制涂色已经要 O(n)O(n)O(n),不可能把 nnn 提到根去算贡献的,那总共需要 O(n2)O(n^2)O(n2)

因此考虑先做 111 为根,每个子树内的答案——这个好做。设 fif_ifi 表示,iii 强制涂色,子树 iii 的方案数。对于边 (u,v)(u,v)(u,v) 合并子树 uuuvvvuuu 即可以在 vvv 涂色合并所有 vvv 为根的连通块(fvf_vfv),也可以舍弃它不要(111)。于是:
fu=∏v=sonu(fv+1)f_u=\prod_{v=son_u}(f_v+1)fu=v=sonu(fv+1)

+1+1+1 也可以类比计算因数和、幂次 +1+1+1

我们发现,节点 uuu 作为根面向的所有节点,除了以 111 为根的子树(已经计算了 fuf_ufu)剩下的就是子树之外的所有点,剩下的点必然是连通的,不妨计算 gug_ugu 表示,uuu 强制选,uuu 子树之外的方案数。
在这里插入图片描述
对于边 (u,v)(u,v)(u,v),因为计算子树外的方案数,所以 gvg_vgvgug_ugu 推过来。上面这幅图,呈现了 gvg_vgv 的推导过程。即在 gug_ugu 基础上,合并上除去 vvv111 为根子树的贡献 fufv+1\dfrac{f_u}{f_v+1}fv+1fu
gv=gu×fufv+1g_v=g_u\times\dfrac{f_u}{f_v+1}gv=gu×fv+1fu

这里出现了除法,但是因为模数 ppp 不一定为质数,很难搞逆元,因此转化为纯乘法:
gv=gu×∏r=sonu,r≠v(fr+1)g_v=g_u\times \prod_{r=son_u,r\neq v} (f_r+1)gv=gu×r=sonu,r=v(fr+1)

用动态数组预存储前后缀积即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9;
ll n,mod;
struct edge
{ll to,next;
}e[N<<1];
ll idx,head[N];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll f[N],g[N];
ll tot[N];
vector<ll>pre[N],suf[N];
void dfs1(ll u,ll fa)
{f[u]=1;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dfs1(v,u);f[u]=f[u]*(f[v]+1)%mod;pre[u].push_back(f[v]+1);suf[u].push_back(f[v]+1);tot[u]++;}for(int i=1;i<pre[u].size();i++)pre[u][i]=pre[u][i]*pre[u][i-1]%mod;for(int i=suf[u].size()-2;i>=0;i--)suf[u][i]=suf[u][i]*suf[u][i+1]%mod;
}
void dfs2(ll u,ll fa)
{ll dfn=0;//遍历儿子的顺序相同 for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dfn++;if(tot[u]==1)g[v]=g[u];else if(dfn==1)g[v]=g[u]*suf[u][dfn]%mod;//suf[u][dfn+1]else if(dfn==tot[u])g[v]=g[u]*pre[u][dfn-2]%mod;//pre[u][dfn-1]else g[v]=g[u]*pre[u][dfn-2]%mod*suf[u][dfn]%mod;g[v]++;//不合并子树外部分,子树内自称1个连通块dfs2(v,u);}
}
int main()
{scanf("%lld%lld",&n,&mod);for(int i=1;i<n;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}dfs1(1,0);g[1]=1;dfs2(1,0);//子树外 for(int i=1;i<=n;i++)printf("%lld\n",f[i]*g[i]%mod);return 0;
}
http://www.dtcms.com/a/328574.html

相关文章:

  • 架构设计:设计原则
  • 第十一节:加载外部模型:GLTF/OBJ格式解析
  • [MySQL数据库] 数据库简介
  • 【虚拟机】VMwareWorkstation17Pro安装步骤
  • Tricentis Tosca 2025.1 LTS 系统要求
  • 华为OD最新机试真题-国际移动用户识别码(IMSI)匹配-(C卷)
  • Terminal Security: Risks, Detection, and Defense Strategies
  • [激光原理与应用-255]:理论 - 几何光学 - CCD成像过程
  • 维文识别技术:将印刷体或手写体的维文文本转化为计算机可处理的数字信息
  • 网络协议组成要素
  • 网络协议——HTTP协议
  • Java锁机制全景解析:从基础到高级的并发控制艺术
  • Navicat更改MySql表名后IDEA项目启动会找原来的表
  • 树结构无感更新及地图大批量点位上图Ui卡顿优化
  • C++ 类型擦除技术:`std::any` 和 `std::variant` 的深入解析
  • 【C++】哈希
  • 终端安全与网络威胁防护笔记
  • 信号反射规律
  • 内存顺序、CAS和ABA:std::atomic的深度解析
  • 亚马逊POST退场后的增长突围:关联与交叉销售的全链路策略重构
  • 语义分割实验
  • python 实现KPCA核主成分分析
  • Ceph的Crush算法思想
  • word——照片自适应框大小【主要针对需要插入证件照时使用】
  • Linux内核进程管理子系统有什么第二十六回 —— 进程主结构详解(22)
  • 深度学习-卷积神经网络-NIN
  • 数据结构:后缀表达式:结合性 (Associativity) 与一元运算符 (Unary Operators)
  • Linux软件编程(三)文件操作-文件 I/O
  • 笔试——Day36
  • Linux应用软件编程---文件操作3(文件IO及其指令、文件定位函数lseek、文件IO与标准IO的比较、缓冲区)