Subtree

题目大意

给定一颗树,你可以选出一些节点,你需要对于每个点求出在强制选这个点的情况下所有选择的点联通的方案数,对给定模数取模。

思路分析

对于这种求树上每一个点方案数的题目,首先考虑换根 DP。

强制钦定树根为 \(1\),设 \(f_i\) 表示在 \(i\) 的子树中选点,\(i\) 强制选,所有选择的点联通的方案数,\(g_i\) 表示在 \(i\) 的子树外选点,\(i\) 强制选,所有选择的点联通的方案数,那么显然点 \(s\) 的答案就是 \(f_s\times g_s\)。

  • 考虑计算 \(f\):

对于叶节点 \(s\),显然 \(f_s=1\),对于非叶节点,容易得出状态转移方程:

\[f_{u}=\prod_{v\in \text{son}_{u}}(f_v+1)
\]

解释一下,\(f_v+1\) 就是 \(u\) 的一个子节点的子树染色的方案数,而 \(u\) 的子树的染色方案数就是所有 \(f_v+1\) 的乘积。

  • 考虑计算 \(g\):

对于根节点 \(1\),显然 \(g_1=1\),对于非根节点,不难得出状态转移方程:

\[g_v=g_{u}\times\frac{f_{u}}{f_v+1},u=\text{fa}_{v}
\]

解释一下,从 \(g_u\) 转移到 \(g_v\),新增的节点就是 \(u\) 的子树去掉 \(v\) 的子树中的点后的所有点,而这些点染色的方案数就是 \(\frac{f_{u}}{f_{v}+1}\),也可以理解为在 \(f_u\) 中去掉所有由 \(v\) 产生的贡献。

但是直接求肯定是没法求的,模数不一定是质数,不一定存在逆元,但是我们发现我们可以将除法改为乘法,也即:

\[g_{v}=g_{u}\times\prod_{p\not =v,p\in \text{son}_u} (f_p+1)
\]

而这个可以通过预处理每个节点的子节点权值的前缀积和后缀积实现。

故我们只需要通过两遍 dfs 就可以在 \(O(n)\) 的时间空间内解决问题。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath> using namespace std;
const int N=200200;
#define int long long int n,mod,in1,in2,idx=1;
int to[N],nxt[N],head[N];
int f[N],g[N]; vector<int> pre[N],suf[N]; void add(int u,int v){
idx++;to[idx]=v;nxt[idx]=head[u];head[u]=idx;
} void dfs_1(int s,int fa){
f[s]=1;
for(int i=head[s];i;i=nxt[i]){
int v=to[i];
if(v==fa) continue;
dfs_1(v,s);
f[s]=f[s]*(f[v]+1)%mod;
pre[s].push_back(f[v]+1);
suf[s].push_back(f[v]+1);
}
for(int i=1;i<pre[s].size();i++)
pre[s][i]=pre[s][i]*pre[s][i-1]%mod;//前缀积
for(int i=suf[s].size()-2;i>=0;i--)
suf[s][i]=suf[s][i]*suf[s][i+1]%mod;//后缀积
} void dfs_2(int s,int fa){
int num=0,x=pre[s].size();
for(int i=head[s];i;i=nxt[i]){
int v=to[i];
if(v==fa) continue;
num++;
if(x==1) g[v]=g[s]+1; //一些特判,可能不需要
else if(num==1) g[v]=g[s]*suf[s][num]%mod+1;
else if(num==x) g[v]=g[s]*pre[s][num-2]%mod+1;
else g[v]=g[s]*(pre[s][num-2]*suf[s][num]%mod)%mod+1;
dfs_2(v,s);
}
} signed main(){
scanf("%lld%lld",&n,&mod);
for(int i=1;i<n;i++){
scanf("%lld%lld",&in1,&in2);
add(in1,in2);add(in2,in1);
}
dfs_1(1,0);
g[1]=1;
dfs_2(1,0);
for(int i=1;i<=n;i++)
cout<<(f[i]*g[i]%mod)<<'\n';
return 0;
}

Subtree 题解的更多相关文章

  1. CF1324F Maximum White Subtree 题解

    原题链接 简要题意: 给定一棵树,每个点有黑白两种颜色:对每个节点,求出包含当前节点的连通图,使得白点数与黑点数差最小.输出这些值. F题也这么简单,咳咳,要是我也熬夜打上那么一场...可惜没时间打啊 ...

  2. Lintcode245 Subtree solution 题解

    [题目描述] You have two every large binary trees:T1, with millions of nodes, and T2, with hundreds of no ...

  3. LeetCode题解之 Subtree of Another Tree

    1.题目描述 2.问题分析 判断一个节点,然后判断子树. 3.代码 bool isSubtree(TreeNode* s, TreeNode* t) { if (s == NULL) return f ...

  4. hdu_4918_Query on the subtree(树的分治+树状数组)

    题目链接:hdu_4918_Query on the subtree 题意: 给出一颗n个点的树,每个点有一个权值,有两种操作,一种是将某个点的权值修改为v,另一种是查询距离点u不超过d的点的权值和. ...

  5. 【LeetCode题解】二叉树的遍历

    我准备开始一个新系列[LeetCode题解],用来记录刷LeetCode题,顺便复习一下数据结构与算法. 1. 二叉树 二叉树(binary tree)是一种极为普遍的数据结构,树的每一个节点最多只有 ...

  6. “玲珑杯”ACM比赛 Round #12题解&源码

    我能说我比较傻么!就只能做一道签到题,没办法,我就先写下A题的题解&源码吧,日后补上剩余题的题解&源码吧!                                     A ...

  7. leetcode & lintcode 题解

    刷题备忘录,for bug-free 招行面试题--求无序数组最长连续序列的长度,这里连续指的是值连续--间隔为1,并不是数值的位置连续 问题: 给出一个未排序的整数数组,找出最长的连续元素序列的长度 ...

  8. LeetCode All in One题解汇总(持续更新中...)

    突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...

  9. LeetCode 333. Largest BST Subtree

    原题链接在这里:https://leetcode.com/problems/largest-bst-subtree/ 题目: Given a binary tree, find the largest ...

  10. NOIP2003题解

    传送门 考查题型 搜索 字符串 模拟 dp T1 神经网络 题目背景 人工神经网络(Artificial Neural Network)是一种新兴的具有自我学习能力的计算系统,在模式识别.函数逼近及贷 ...

随机推荐

  1. 【Oracle】使用PL/SQL实现冒泡排序

    [Oracle]使用PL/SQL实现冒泡排序 一般来说,SQL要排序的话直接使用order by即可 不一般来说,就是瞎搞,正好也可以巩固自己的数据结构基础 存储包内容如下 规范: create or ...

  2. 微调用于多语言 ASR 的 MMS 适配器模型

    新内容 (06/2023): 这篇博文受到 "在多语言 ASR 上微调 XLS-R" 的强烈启发,可以看作是它的改进版本. Wav2Vec2 是自动语音识别 (ASR) 的预训练模 ...

  3. java查询sql动态查询需要的字段

    方法一:使用"trim"标签. <select id="selTest" parameterType="mocha.framework.enti ...

  4. 【Spring boot】 @Value注解

    一.不通过配置文件的注入属性 1.1 注入普通字符串 直接附在属性名上,在 Bean 初始化时,会赋初始值 @Value("normal") private String norm ...

  5. kali下对压缩包的压缩与解压(转)

    kali linux 压缩文件解压缩命令(包含7z) tar tar 解包:tar xvf FileName.tar 打包:tar cvf FileName.tar DirName (注:tar是打包 ...

  6. 玩转 PI 系列-如何在 Rockchip Arm 开发板上安装 Docker Tailscale K3s Cilium?

    概述 618 买了几个便宜的 Purple PI OH 开发板 (500 块多一点买了 3 个), 这个开发板类似树莓派,是基于 Rockchip(瑞芯微) 的 rx3566 arm64 芯片.如下: ...

  7. tensorflow-2.7-M1-安装依赖openblas问题

    问题描述 安装过程 conda create -n conda-forge-tensorflow conda-forge::tensorflow conda info -e conda activat ...

  8. 基于Prometheus搭建监控平台

    目录 前言 配置server单节点 prometheus.service 配置node节点 配置mysql监控 在数据库中添加exporter账户 修改mysql_exporter的配置 添加serv ...

  9. Java基础实现加油站圈存机系统

    加油站圈存机系统 ​ 对于加油卡而言,圈存是将用户账户中已存入的资金划转到所持的加油卡上后方可使用.通俗一点的说法就是您在网点把钱存入主卡中,再分配到下面的副卡,由于副卡都在使用车辆的驾驶员手中,需要 ...

  10. 关于Vue的就地更新策略的解析

    在Vue中使用v-for渲染列表时,默认使用就地更新策略.该策略默认是基于索引的,规定在列表绑定的数据元素顺序变化时,不会重新创建整个列表,而只是更新对应DOM元素上的数据.以下代码实现了一个TODO ...