P11363 [NOIP2024] 树的遍历
P11363 [NOIP2024] 树的遍历
题意
给你一个 \(n\) 个点的树,定义边与边相邻当且仅当它们有一个公共端点。一条合法 dfs 路径从某条边出发,如果存在未访问的相邻的边,则必须选择其中一条边走,否则回溯。
给你 \(k\) 个关键边,问以这些关键边为起点可以生成多少不同构的有标号无向边 dfs 树。
\(n \le 10^5\)。
思路
参考 @喵仔牛奶 的做法。
我们给边重新编号,边 \(u\) 表示点 \(u\) 连向父亲的那条边。假设原树以 \(1\) 为根,那么编号 \(\in[2,n]\)。由题目定义,\(u\) 与它的父亲 \(fa_u\),它的兄弟 \(bro_u\) 和它的儿子 \(son_u\) 相邻。以下直接写编号的都是指边,不是点。
当 \(k=1\) 时是好解决的,就是 \(\prod_{i=1}^n (d_i-1)!\)。其中 \(d_i\) 是点 \(i\) 的度数。
对于每个点,它所有边的访问顺序是一个排列,而当我们访问到点 \(u\) 的某条连边时,一定是由某条边过来的,那么由前面的操作我们钦定了这个排列的第一条边。然后点 \(u\) 可以自由决定其他边的访问顺序,所以就是 \(d_u-1\) 的连乘了。
当 \(k>1\) 时,问题是 \(k\) 个点出发可以生成多少不同构的树,也即有多少不同构的树可以以 \(k\) 个点其中一些点出发生成。容易想到容斥,但是首先要发掘更多性质。
我们想知道每个点的连边的排列和 dfs 树的关系。显然 dfs 树可能有同构,因此不是双射。
\(p_i\) 遍历完它的子树之后就会走到 \(p_{i+1}\)。因此 \(p_i\) 和 \(p_{i+1}\) 一定有连边!所以排列对 dfs 树上的一条链。
显然 reverse 排列不会改变 dfs 树。
那么我们可以容斥,计算有多少种合法的定排列头(定起点方向)的方案,然后如果某个点存在两个定起点方向的方案,它们成为排列头时的排列互为 reverse 的排列,dfs 树就重构了,需要减掉。计算起点边属于 \(k\) 个关键边条件下的方案。
因为每个点对应一个边的排列,所以我们按照点分析每个排列。
显然合法的点的连边的访问的排列顺序对应唯一的合法的 dfs 树,且对应唯一的出发边,但是合法的 dfs 树可以对应多个合法的每个点的连边访问排列顺序。因此考虑由 dfs 树推到哪些排列顺序然后推到哪些边可以做起点。
排列顺序只要一直往排列的第一条边的方向走就可以到达起点边了。换句话说,按照每个点的排列的最后一条边的方向画箭头,就可以得到以起点边为“根”的外向树。根必须是特殊边。
相当于要给每个排列选择两个端点,使得向着任意一个端点定向后是一棵外向树,且树根是特殊边。求排列方案数。
假如已经钦定了集合 \(S\) 的边可以作为树根,求出方案数。然后乘上 \((-1)^{|S|+1}\) 的容斥系数。
若边 \(x \in S\),\(x\) 将原树分成两部分,这两部分的点的排列的一个端点方向必须向 \(x\)。
因此所有 \(x \in S\) 一定在一条链上。
在这条链上的点(不含端点)的贡献是 \((d_u-2)!\),其他点是 \((d_u-1)!\)。可以变形成:
\]
前半是简单的,我们要计数后半部分。
进行树形 DP。按照点 DP。DP 的时候带上容斥系数。
设 \(c_i = \frac{1}{d_u-1}\)。
剩下的,和参考题解一样。
时间复杂度线性。
code
代码是不难写的,性质是很难找的。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
constexpr int N=1e5+7,mod=1e9+7,Max=1e5;
int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }
void _add(int &a,int b) { a=add(a,b); }
int mul(int a,int b) { return 1ll*a*b%mod; }
void _mul(int &a,int b) { a=mul(a,b); }
int testid,t,n,k;
int u,v;
typedef pair<int,int> pii;
#define fi first
#define se second
vector<pii> to[N];
int col[N];
int x;
int fac[N],inv[N];
void _init() {
fac[0]=1;
rep(i,1,Max) fac[i]=mul(fac[i-1],i), inv[i]=(i>1 ? mul(inv[mod%i],(mod-mod/i)) : 1);
}
int f[N];
int ans;
void init() {
rep(i,1,n) to[i].clear();
memset(col,0,sizeof(col));
ans=0;
}
void dfs(int u,int fa,int op) {
int w=inv[to[u].size()-1], s=mod-op;
f[u]=mod-op; _add(ans,op);
for(auto p : to[u]) {
int v=p.se, id=p.fi;
if(v^fa) {
dfs(v,u,col[id]);
_add(ans,mod-mul(w,mul(s,f[v]))), _add(f[u],mul((op ? 0 : f[v]),w)), _add(s,f[v]);
}
}
}
void main() {
sf("%d%d",&testid,&t);
_init();
while(t--) {
sf("%d%d",&n,&k);
init();
rep(i,1,n-1) {
sf("%d%d",&u,&v);
to[u].push_back({i,v});
to[v].push_back({i,u});
}
rep(i,1,k) {
sf("%d",&x);
col[x]=1;
}
dfs(1,0,0);
rep(i,1,n) _mul(ans,fac[to[i].size()-1]);
pf("%d\n",ans);
}
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
wing_heart :: main();
}
P11363 [NOIP2024] 树的遍历的更多相关文章
- 数据结构--树(遍历,红黑,B树)
平时接触树还比较少,写一篇博文来积累一下树的相关知识. 很早之前在数据结构里面学的树的遍历. 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍 ...
- YTU 3023: 树的遍历
原文链接:https://www.dreamwings.cn/ytu3023/2617.html 3023: 树的遍历 时间限制: 1 Sec 内存限制: 128 MB 提交: 3 解决: 2 题 ...
- 团体程序设计天梯赛-练习集L2-006. 树的遍历
L2-006. 树的遍历 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历 ...
- leetcode404-----简单的树的遍历
Find the sum of all left leaves in a given binary tree. Example: 3 / \ 9 20 / \ 15 7 There are two l ...
- pat L2-006. 树的遍历
L2-006. 树的遍历 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历 ...
- L2-006. 树的遍历
题目链接:L2-006. 树的遍历 今天一神给我手敲二叉树模板,瞬间就领悟了,感觉自己萌萌哒! 看上去很直观! #include <iostream> #include <cstdi ...
- js实现对树深度优先遍历与广度优先遍历
深度优先与广度优先的定义 首先我们先要知道什么是深度优先什么是广度优先. 深度优先遍历是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续 ...
- L2-006 树的遍历 (25 分) (根据后序遍历与中序遍历建二叉树)
题目链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805069361299456 L2-006 树的遍历 (25 分 ...
- 团体程序设计天梯赛 L2-006. 树的遍历 L2-011. 玩转二叉树
L2-006. 树的遍历 #include <stdio.h> #include <stdlib.h> #include <string.h> #include & ...
- L2-006 树的遍历 (后序中序求层序)
题目: 给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出一个正整数N(≤30),是二叉树中结点的个数.第二行给出其后序遍历序 ...
随机推荐
- 生成库文件 virtual studio
简介 如果我们把我们要常用的文件生成自己的动态库的话,我们将加速我们的第一代开发过程. 核心内容 参考链接: https://www.cnblogs.com/lanhaicode/p/10798385 ...
- python print 输出重定向
简介 print 重定向的功能,很实用,记录一下 参考链接 https://www.cnblogs.com/marsggbo/p/10293484.html code import sys impor ...
- SciTech-EECS-PowerRelay功率继电器-正确使用方法:交流侧并联TVR压敏电阻防浪涌电路灭火花+直流侧反向并联一个二极管或RC电路吸收感生反电动势
注意:并联二极管能防止继电器感生电动势的冲击但会延长继电器的释放时间3~5倍. SSR(Solid State Relay)固态继电器 参考OMRON的官方文档 电磁继电器 1.两篇重要参考文献: P ...
- [题解] AT_ABC406_E Popcount Sum 3
洛谷题目传送门 ATcoder题目链接 题目大意 给你正整数 \(N\) 和 \(K\). 求所有不超过 \(N\) 且满足以下条件的正整数 \(x\) 的 和(取模 \(998244353\)): ...
- Windows10系统文件出现安全警告的问题
有一位电脑基地的小伙伴在使用windows10专业版系统的时候,发现这样一个问题,就是每次安装一个程序的时候,总是会弹出提示框,提示"打开文件-安全警告,你要打开此文件吗",这是说 ...
- 【Roslyn C#】Runtime环境Unity读取字符串代码
Roslyn C# 下载地址:https://files-cdn.cnblogs.com/files/sanyejun/RoslynC_RuntimeCompiler.zip 使用示例 using S ...
- K8s Pod 多种数据存储方式
由于容器的生命周期可能很短,会被频繁地创建和销毁.那么容器在销毁时,保存在容器中的数据也会被清除.这种结果对用户来说,在某些情况下是不乐意看到的.为了持久化保存容器的数据,kubernetes引入了V ...
- redis里面的分布式锁的作用和分布式锁的实现
分布式锁在 Redis 中的作用 在分布式系统中,多个服务实例或进程可能会同时访问和操作共享资源,若缺乏有效的同步机制,就会引发数据不一致.并发冲突等问题. 1.保证数据一致性:在分布式环境下,多个客 ...
- 🌐 从零构建高可用 API 网关:鉴权、路由、性能优化全解析
从零构建高可用 API 网关:鉴权.路由.性能优化全解析 作者:古渡蓝按 技术栈:Spring Cloud Gateway + redis + Nacos + 自定义鉴权 技术栈:微信公众号(深入浅出 ...
- oracle存儲過程掉用HTTP接口
CREATE OR REPLACE PROCEDURE FSMT.P_GET_HTTP_RES/* 描述:存儲過程掉用HTTP接口 作者:Janus 日期:2024-08-15*/( M_DOC_NO ...