几只毛毛虫?

news/2025/3/4 23:27:35/文章来源:https://www.cnblogs.com/onlyblues/p/18751548

几只毛毛虫?

题目描述

一天,在生物课上,老师带着小羊和他的同学去公园观察动物。

他看到了草丛里有很多毛毛虫,于是他想,毛毛虫有什么特征呢?

于是他把一条毛毛虫抽象成了一棵有 $n$ 个节点的树。树是一个有 $n$ 个点 $n−1$ 条无向边组成的连通图。

这棵树被称为一条毛毛虫,当且仅当:树上存在一条路径 $u_1 \to u_2 \to \cdots →u_k$ $(k \geq 2)$ ,使得 $u_i, u_{i+1}$ $(1 \leq i < k)$ 有边,且 $u_1,u_2 , \ldots, u_k$ 两两不同;同时对于树上任一点 $v$ ,路径上存在一点到 $v$ 的距离不超过 $1$ 。

因为在 “是毛毛虫吗?” 中小羊画了太多毛毛虫,现在小羊觉得画毛毛虫太无聊了,于是他随便画了 $T$ 棵树,并一棵一棵地问你:用橡皮擦擦掉一些边和点的话,能形成多少只不同的毛毛虫呢?

只要两条毛毛虫包含了不同的节点,我们就认为这两条毛毛虫是不同的。

因为不同的毛毛虫的数量可能很多,请将答案关于 $10^9+7$ 取模后输出。

注意:我们认为单点不形成毛毛虫,因为找不到满足定义的路径。两条毛毛虫不同,当且仅当至少存在一个点在其中一条毛毛虫中,而不在另一条毛毛虫中。

输入描述:

第一行输入一个整数 $T (1 \leq T \leq 10^4)$ ,表示小羊画的树有 $T$ 棵。

接下来输入 $T$ 棵树。

对于每一棵树,第一行输入整数 $n$ $(2 \leq n \leq 10^5)$ ,表示这棵树的顶点的个数,这棵树的顶点为 $1,2, \ldots ,n$。

接下来的 $n−1$ 行,每行输入两个整数 $u_i,v_i$ $(1 \leq u_i, v_i \leq n)$ ,表示顶点 $u_i, v_i$ 之间连接了无向边。

保证输入的每一个图都是一棵树,且所有样例对应 $n$ 的和不超过 $2 \times 10^5$ 。

输出描述:

对于每一棵树,输出一行,在该行输出一个整数,表示该树通过擦去边和点,可以得到的不同的毛毛虫的数量,并将结果关于 $10^9 + 7$ 取模。

示例1

输入

2
4
1 2
2 3
2 4
4
1 2
2 3
3 4

输出

7
6

说明

对于第一棵树,以下顶点集和原图中集内点之间存在的边构成毛毛虫: 

$\{1,2\},\{2,3\},\{2,4\},\{1,2,3\},\{1,2,4\},\{2,3,4\},\{1,2,3,4\}$

对于第二棵树,以下顶点集和原图中集内点之间存在的边构成毛毛虫: 

$\{1,2\},\{2,3\},\{3,4\},\{1,2,3\},\{2,3,4\},\{1,2,3,4\}$

故答案分别为 $7,6$。

 

解题思路

  官方题解看了好久才看懂。题解写起来也挺麻烦的,要是有不懂的地方可以留言。

  容易知道,要判断一棵树是不是毛毛虫,可以选择树的直径作为题目定义中的路径,然后再判断所有点到直径的距离是否不超过 $1$ 即可。现在可以删除一些点和边,问有多少棵不同的树是毛毛虫,等价于问有多少个子图(也是一棵树)是毛毛虫。

  如果一个子图是毛毛虫,那么一定存在一条树的直径,而这条直径可以是树上任意一条路径。为此容易想到可以根据不同的路径作为直径来对子图分类。假设现在有一条路径,如果要以这条路径作为子图的直径,且这个子图是毛毛虫,应该满足什么条件?

  以下图为例,如果想让红色点所构成的路径作为子图的直径,那么该路径的两个端点的度数必须是 $1$(否则必然存在比该路径更长的直径),因此需要删除端点上除路径上节点外的其余所有点(也就是虚线三角形表示的子树)。接下来,要使得子图是毛毛虫,路径上非端点的点只能保留与其直接相邻的节点(即蓝色的点),否则会存在到该路径距离超过 $1$ 的节点。每个相邻节点可以选或不选。

  当一条路径作为直径时,有多少个子图是毛毛虫呢?首先两个端点不能选除路径外的任何点,因此只有 $1$ 种方案。考虑路径上的非端点节点,假设一个节点 $u$ 的度数为 $d_u$,那么选择与其直接相邻的点的方案数为 $2^{d_u - 2}$(减 $2$ 是指不考虑路径上与其相邻的点,因为这个是必选的)。因此总的方案数是 $\sum\limits_{u}{2^{d_u - 2}}$,这里的 $u$ 是指路径上非端点的节点。

  但这种统计的方法会有重复,参考下图,两条不同的路径会得到同一个是毛毛虫的子图。

  可以发现与端点相邻的节点的统计方法会导致重复,因此我们可以参考官方题解中的方法,我们不要管直径的两个端点,而是选择直径上的非端点的节点。当然这条路径还是会存在两个端点的,不过要注意的是选出路径的端点在直径上是非端点节点,它们与直径的端点相连。此时路径的这两个端点的方案数应该都是 $2^{d_u-1}-1$,表示从直接与其相邻节点中(不含路径上与其相邻的另外一个节点)选出非空的方案数(因为要至少延伸出一个点作为直径的端点)。然后其余的每个节点的方案数还是 $2^{d_u - 2}$。

  因此我们可以枚举所有的路径,然后求出以该路径为直径的非端点节点,且构成毛毛虫的子图数量。所有路径的结果求和就是要求的答案。显然我们不可能真的枚举所有可能的路径,这样会超时。继续用分类的思想,将所有路径按照两个端点的 lca 进行分类(假设原树以节点 $1$ 为根)。接着就是遍历整个树,以每个节点 $u$ 作为路径两个端点的 lca 进行 dp。

  定义 $f(u)$ 表示 $u$ 与其子树中每个节点构成的链,作为直径上非端点节点时(其中子树中的节点与直径端点相连,而 $u$ 不与直径端点相连),方案数的和。太绕口了,看下面的图就明白了。

\begin{align*}
f(u)=2^{d_u-2} \times \left( (2^{d_{v_1}-1}-1) + (2^{d_{v_2}-1}-1) + 2^{d_{v_1}-2} \cdot (2^{d_{v_3}-1}-1) + 2^{d_{v_1}-2} \cdot (2^{d_{v_4}-1}-1) \right)
\end{align*}

  假设 $u$ 的儿子为 $v_1, v_2, \ldots v_m$。以 $u$ 作为两端点的 lca 的路径可以分成两类,第一类是以 $u$ 为端点的路径,方案数就是 $\sum\limits_{i=1}^{m}{(2^{d_u-1}-1) \cdot (f(v_i) + 2^{d_{v_i}-1}-1)}$。另一类是 $u$ 作为路径上的非端点的路径(意味着两个端点要从 $u$ 两个不同儿子的子树中选),方案数是 $\sum\limits_{i=2}^{m}{2^{d_u-2} \cdot (f(v_i) + 2^{d_{v_i}-1}-1) \left( \sum\limits_{j=1}^{i-1}{f(v_j) + 2^{d_{v_j}-1}-1} \right)}$(第二部分的求和可以用前缀和维护)。

  根据定义 $f(u) = 2^{d_u-2}\left(\sum\limits_{i=1}^{m}{f(v_i) + 2^{d_{v_i}-1}-1}\right)$。

  注意到上面的做法只能求出直径长度大于等于 $3$ 的毛毛虫子图数量。对于直径长度等于 $1$ 和 $2$ 的情况需要单独处理。

  其中当直径长度等于 $1$ 时,此时毛毛虫只能是两点一边的形式,这样的子图数量就是边的数量即 $n-1$。当直径长度等于 $2$ 时,此时直径应由三个节点构成,考虑直径中间的节点,那么直径长度为 $3$ 且子图是毛毛虫的方案数就是 $2^{d_{u}}-d_u-1$。其中 $2^{d_u}$ 表示选择 $u$ 与直接相邻节点的所有方案数,由于要保证直径的长度恰好为 $3$,因此要排除选择 $0$ 和 $1$ 个相邻节点的情况。

  剩下的细节可以看代码。  

  AC 代码如下,时间复杂度为 $O(n)$:

#include <bits/stdc++.h>
using namespace std;typedef long long LL;const int N = 2e5 + 5, M = N * 2, mod = 1e9 + 7;int h[N], e[M], ne[M], idx;
int p2[N];
int d[N], f[N];
int ans;void add(int u, int v) {e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}void dfs(int u, int p) {f[u] = 0;int sum = 0;for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == p) continue;dfs(v, u);ans = (ans + sum * (f[v] + p2[d[v] - 1] - 1ll) % mod * p2[d[u] - 2]) % mod;ans = (ans + (p2[d[u] - 1] - 1ll) * (f[v] + p2[d[v] - 1] - 1)) % mod;sum = (sum + f[v] + p2[d[v] - 1] - 1) % mod;}f[u] = 1ll * p2[d[u] - 2] * sum % mod;
}void solve() {int n;cin >> n;memset(h, -1, n + 1 << 2);memset(d, 0, n + 1 << 2);idx = 0;for (int i = 0; i < n - 1; i++) {int u, v;cin >> u >> v;add(u, v), add(v, u);d[u]++, d[v]++;}p2[0] = 1;for (int i = 1; i <= n; i++) {p2[i] = p2[i - 1] * 2ll % mod;}ans = n - 1;for (int i = 1; i <= n; i++) {ans = (ans + p2[d[i]] - d[i] - 1) % mod;}dfs(1, 0);cout << (ans + mod) % mod << '\n';
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}

 

参考资料

  小羊杯 Round 2 题解:https://blog.nowcoder.net/n/c5934ffa9a6d4c29b79cfaabb777aa3f

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/893686.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C基础1

基础回顾 Hello World到底是什么🤔 //预处理 #include <stdio.h> //include是找的意思,找到stdio这个头文件//.h是头文件的后缀,.c是c语言源文件的后缀,.cpp是c++源文件的后缀//std是standard标准,i是input输入,o是output输出 //程序的主入口 int main( ) //int表示…

我的Redission使用初体验

本文记录作者第一次使用Redission实现分布式锁的体验。对于碰到的问题进行了一些总结。当我们实际使用redis手写实现分布式锁时,会产生不可重入、不可重试、超时释放和主从一致的问题,此时redission为我们提供的锁完美达成以上要求,适合我们生产时使用。Redisson的实际使用 …

Power Automate 格式化JSON时字段为空

前言最近,在使用Power Automate处理JSON的时候,碰到一个错误。正文1.处理JSON的过程,一般是这样的,如下图:2.测试一下,这样,我们就很容易操作JSON字符串中的属性了,如下图:3.但是,大家在开发中,碰到过这样的问题没有?4.详细的错误信息,如下图:[{"message&qu…

ChromaDB

ChromaDB是一个开源的嵌入式向量数据库,专为存储和查询高维向量数据而设计,特别适用于与大型语言模型(LLMs)和嵌入模型(Embeddings)相关的应用场景 安装pip install chromadb启动服务chroma run --path ./chroma_data --host 0.0.0.0 --port 8000说明: --path ./chroma_…

如何为 Power Automate 配置 Azure Key Vault 权限

前言最近,在Power Automate中使用Azure Key Vault,然后,就需要配置一下AKV的权限。正文1.我们在Azure Portal里新建一个Key vault,如下图:2.进入Access policies,点击Create,如下图:3.如果只是为Power Automate使用,只需要勾选红框的权限就好,如下图:4.搜索需要的服…

P2671 [NOIP 2015 普及组] 求和

好题,思想很好。 首先看到这个题一个显然的思路是\(O(n^3)\)的暴力,直接枚举三个判断可行性计算贡献。 思考简单的优化,题目条件限制z-y=y-x变形可得\(y=\frac{z-x}{2}\),由于\(y\)一定是正整数,所以\(z\)与\(x\)正负性相同,考虑将原数组拆分为奇数和偶数两个集合,并在两…

web开发 辅助学习管理系统开发日记 day8

今日完成员工上传头像功能,删除员工功能,以及调试阿里云oss服务器。 Q1:如何配置阿里云服务器 1.创建阿里云服务器并且搭建好配置 2.引入依赖 在oss简介首页引入 3.从官网复制粘贴出上传文件这个功能所在工具类 4.在控制层里面进行上传 Q2 批量删除员工功能 1.在controller层…

pycharm添加conda环境的解释器时找不到python.exe

pycharm添加conda环境的解释器时找不到python.exe 先找到 anaconda 安装目录下的 condabin/conda ,然后加载环境, 加载之后下面就有了conda环境,可以进行选择

md5.exe WriteUp

WriteUp 题目信息 名称:md5.exe 分类:Reverse 描述:找到程序的flag题目链接: https://pan.baidu.com/s/1u8bGbKcUF6_gLaw63L3jyA?pwd=h8r5 提取码: h8r5解题思路 首先用DIE查看该文件,发现该文件没有壳,且是32位exe文件。所以可以直接用32位IDA打开该文件,并按F5对main函…

xor.exe WriteUp

WriteUp 题目信息 名称:xor.exe 分类:Reverse 描述:找到程序的flag题目链接: https://pan.baidu.com/s/1u8bGbKcUF6_gLaw63L3jyA?pwd=h8r5 提取码: h8r5解题思路 首先用DIE查看该文件,发现该文件没有壳,且是32位exe文件。所以可以直接用32位IDA打开该文件,并按F5对main函…

原型链图

啥都不说,先上图:上面展示了各个对象之间是怎样通过显式原型(prototype)及隐式原型([[prototype]],通过__proto__访问)来串联起来的。

【C++库函数】C++生成高质量随机数的方法

#include <iostream> #include <random>int main() {// 第一步:初始化真随机种子(使用硬件熵源)std::random_device rd; // 第二步:选择高性能引擎(推荐 mt19937)std::mt19937 gen(rd()); // 用 random_device 的输出来种子初始化引擎// 第三步:定义分布(…