思路:

从简单情况考虑,所以考虑当 \(k = 1\) 时的答案。可以发现,当我们有了一个关键边之后,到达每个点的边都是确定的,并且,这个点的邻边在新树上会构成一条链,所以除了进入这个点的边以外,其他的邻边可以任意安排顺序,所以这个点的贡献就是 \((deg_u - 1)!\) 。所以总贡献就是:

\[\prod_{u\in T}(deg_u-1)!
\]

再看到部分分,考虑 \(k=2\) 的情况,一个直接的想法是将 \(k=1\) 时的答案乘以\(2\),但是这是错的,通过样例2解释你就会发现,这种情况可能会有重复的计数出现。所以考虑能同时满足两个关键点生成的树有什么特点:显然的,这两个关键边之间会形成一条链,自己画一下图就可以发现,链上的每一个点不仅入边确定了,出边也是确定的,这样才能满足这颗树会同时出现在第一个关键边和第二个关建边的贡献内。所以链上的每个点的贡献就为 \((deg_u-2)!\) ,所以令这条链为 \(L\) (不包括链的两个端点)。 所以形式化的,重复的树的数量就为:

\[\prod_{u\in T}(deg_u-1)!\times\prod_{u\in L}(deg_u-1)^{-1}
\]

这就促使我们用容斥原理解决这道题了,钦定一个关键边集 \(S\) ,求可以同时以这些关键边为根的新树个数。个数记为 \(g(S)\) ,所以最终答案为:

\[\sum_{S}(-1)^{|S|+1}g(S)
\]

那么现在考虑一般情况,可以发现的是,如果以某个点为根有至少3个子树存在钦定关键边,那么重复的数量就为\(0\)。也就是我们容斥中钦定的关键边必然构成原树上的一条链才是有有效贡献的

如果我们尝试去确定这条链,你会发现每一条链的贡献最终形式化的表达就是当 \(k=2\) 时的那个式子。

我们考虑把 \(\prod_{u\in T}(deg_u-1)!\) 这部分提出来最后乘。所以此时就可以记点\(u\)的点权为:\(c_u=(deg_u-1)^{-1}\),所以一条链的贡献就是这条链上的点权之积。定义 \(f_u\) 表示钦定的关键点集构成的链经过 \(u\) ,且恰好其中一个端点在 \(u\) 子树内(包括\(u\))的带权方案数(也就是所有可能的链的方案和)。我们分情况转移(注意转移的时候我们先不考虑容斥系数指数上面多出来的\(+1\),留到最后统计答案的时候处理):

1. 若边 \((u,fa_u)\) 可以作为钦定的关键边,所以转移贡献可以由一下几部分加起来组成:只选 \((u,fa_u)\),只选子树内的链 \(c_u\sum_vf_v\),两者同时选 \(-c_u\sum_vf_v\),所以总贡献就为\(-1\)。

2. 若不是上述情况,就只能只选子树内的链,贡献为 \(c_u\sum_vf_v\)。

所以形式化的:

\[f_u =
\begin{cases}
-1,\left(if\ (u,fa_u)\ is\ key \ edge\right)\\
c_u\sum_vf_v\\
\end{cases}
\]

现在考虑如何统计答案。

朴素的方法是合并子树,即对于点\(u\),我们考虑枚举其子树,将子树中的方案乘起来,然后合并枚举到的子树继续处理。设 \(s\) 为当前遍历到的子树贡献和,所以形式化的:

若\((u,fa_u)\)可以作为关键边,那么先让 \(ans\leftarrow ans+1\) (只选这条边),\(s=-1\)。

接着枚举儿子 \(v\) :

\[ans\leftarrow ans-c_u\times s\times f_v
\]
\[s\leftarrow s+f_v
\]

枚举儿子贡献 \(ans\) 的时候是减法是因为我们把容斥系数上面的 \(-1\) 留到了贡献答案的时候来处理。

总时间复杂度 \(O(Tn)\) 。

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
return x * f;
}
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
int n,k,fac[N],inv[N],vis[N],ans,f[N];
std::vector<std::pair<int,int> > G[N];
void init()
{
fac[0] = 1;
for(register int i = 1;i < N;++i) fac[i] = fac[i - 1] * i % MOD;
inv[0] = inv[1] = 1;
for(register int i = 2;i < N;++i){
inv[i] = MOD - 1ll * (MOD / i) * inv[MOD % i] % MOD;
}
} void dfs(int u,int fa,int fl)
{
int s = -fl;
f[u] = -fl;
ans += fl;
int cost = inv[G[u].size() - 1]; for(auto tmp : G[u])
{
int v = tmp.first,id = tmp.second;
if(v == fa) continue;
dfs(v,u,vis[id]);
f[u] = (f[u] + (fl ? 0 : f[v]) * cost % MOD + MOD) % MOD;
ans = (ans - ((cost * s % MOD) * f[v] % MOD) + MOD) % MOD;
s = (s + f[v] + MOD) % MOD;
}
} signed main()
{
int c,T;
c = read();
T = read();
init();
while(T--)
{
n = read();
k = read();
for(int i = 1;i < n;++i)
{
int u,v;
u = read();
v = read();
G[u].emplace_back(v,i);
G[v].emplace_back(u,i);
} for(int i = 1;i <= k;++i)
{
int id;
id = read();
vis[id] = 1;
} dfs(1,0,0); for(int i = 1;i <= n;++i) ans = (ans * fac[G[i].size() - 1]) % MOD;
std::cout << ans << '\n';
for(int i = 1;i <= n;++i){
G[i].clear();
vis[i] = 0;
}
ans = 0;
}
return 0;
}

P11363 [NOIP2024] 树的遍历 题解的更多相关文章

  1. 数据结构--树(遍历,红黑,B树)

    平时接触树还比较少,写一篇博文来积累一下树的相关知识. 很早之前在数据结构里面学的树的遍历. 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍 ...

  2. YTU 3023: 树的遍历

    原文链接:https://www.dreamwings.cn/ytu3023/2617.html 3023: 树的遍历 时间限制: 1 Sec  内存限制: 128 MB 提交: 3  解决: 2 题 ...

  3. 团体程序设计天梯赛-练习集L2-006. 树的遍历

    L2-006. 树的遍历 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历 ...

  4. leetcode404-----简单的树的遍历

    Find the sum of all left leaves in a given binary tree. Example: 3 / \ 9 20 / \ 15 7 There are two l ...

  5. pat L2-006. 树的遍历

    L2-006. 树的遍历 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历 ...

  6. L2-006. 树的遍历

    题目链接:L2-006. 树的遍历 今天一神给我手敲二叉树模板,瞬间就领悟了,感觉自己萌萌哒! 看上去很直观! #include <iostream> #include <cstdi ...

  7. js实现对树深度优先遍历与广度优先遍历

    深度优先与广度优先的定义 首先我们先要知道什么是深度优先什么是广度优先. 深度优先遍历是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续 ...

  8. L2-006 树的遍历 (25 分) (根据后序遍历与中序遍历建二叉树)

    题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805069361299456 L2-006 树的遍历 (25 分 ...

  9. 团体程序设计天梯赛 L2-006. 树的遍历 L2-011. 玩转二叉树

    L2-006. 树的遍历 #include <stdio.h> #include <stdlib.h> #include <string.h> #include & ...

  10. L2-006 树的遍历 (后序中序求层序)

    题目: 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出一个正整数N(≤30),是二叉树中结点的个数.第二行给出其后序遍历序 ...

随机推荐

  1. 资源类型Scheme

    资源类型Scheme 1.介绍 当我们操作资源和 apiserver 进行通信的时候,需要根据资源对象类型的 Group.Version.Kind 以及规范定义.编解码等内容构成 Scheme 类型, ...

  2. [nodejs原型链污染及绕过]校赛第四次纳新赛 bypass WP

    前言 赛后自学了nodejs原型链污染后来尝试做这个题,难度不算太大,但是绕过姿势非常奇怪没见过,写一篇总结记录一下做法 wp 首先打开环境发现是一个登录框,题目有附件我们下载查看附件 最关键的就是c ...

  3. Centos7.x根分区扩容

    背景说明 我们在部署好的系统中,随着数据的不断增加, 发现根分区频繁出现满载问题,这种情况下,我们需要对根分区进行扩容. 方案说明 • 使用空闲磁盘扩容到根分区 • 使用空闲的分区扩容到根分区 • 使 ...

  4. C# WinForms 实现打印监听组件

    一.组件简介 打印监听组件是一款集成于 Windows 桌面环境的打印任务管理与监控工具,适用于企业级应用场景.它不仅支持多打印机任务的实时监控,还能通过 WebSocket 与外部系统集成,实现自动 ...

  5. Step-by-step FTP to ABAP Proxy

    引自:https://wiki.scn.sap.com/wiki/display/XI/Step-by-step+FTP+to+ABAP+Proxy ABAP Server Proxy Area: S ...

  6. MongoDB入门实战教程(1)

    对于后端开发工程师,NoSQL是一个需要掌握的技术点,而NoSQL中比较火热的技术当属MongoDB.欢迎入门MongoDB,进入无模式的文档数据库世界. 1 关于MongoDB 通过下面几个问题,我 ...

  7. android多活动练习--人品计算器

    效果图如下: 第二个页面: 显示结果和姓名.性别有关,代码如下: activity_main.xml: 1 <?xml version="1.0" encoding=&quo ...

  8. mybatis-plus单表操作

    查询统计 QueryWrapper queryWrapper = new QueryWrapper<>().groupBy("type_id").select(&quo ...

  9. Luogu P9588 队列 题解

    P9588 队列 考虑转化问题,将原问题转化为一个长度为 \(q\) 的序列.序列中 \(x\) 表示一段 \(1\sim x\) 的区间. 操作 \(1\) 每次增加时,输入 \(x\),在数组末尾 ...

  10. 解决Ubuntu上使用fsck命令时遇到的“The superlock could not be read......”的问题

    问题产生原因:我也不太清楚,可能是给硬盘分区的时候出的问题. 问题解决方法:依次执行以下的命令,请根据实际情况调整存储设备名称. 注意:下面的操作会清空硬盘所有数据,请根据自己的需求来判断是否需要执行 ...