Luogu P11363 NOIP2024 树的遍历 题解 [ 紫 ] [ 树形 dp ] [ 组合计数 ] [ adhoc ]
树上遍历:CCF 难得一遇的好题!
参考了洛谷的第一篇题解,所以思路会有点相似。
部分分
当 \(k=1\) 时,显然方案总数为 \(\prod_{i=1}^{n}(d_i-1)!\),因为进入一个子节点后可以以任意顺序遍历它的所有出边。
观察
当遍历出来的树的形态确定时,能形成这棵树的边组合在一起一定是一条链,且这条链是这棵树上两个不同的叶子节点之间的链。
这个手模几组样例就应该理解了,证明比较感性,观察可得一个节点的所有边一定在遍历出的树中是一条链。所以我们一定要从一个边以一条链的路径走到其他的所有边,于是这条链上中间的边就是无法作为起点的,因为去了某个边之后就回不来了。
每个遍历生成树都一定都这样的一条链,且一旦确定这条链,那么生成的新树就有 \(\prod_{i=1}^{n}(d_i-1-[i\in V])!\) 种,其中 \(V\) 表示这条链上的节点。理解就是进入一个子节点之后,在链上的点已经确定了它从哪里来,并且确定了它最后走的边是哪条,因此是 \(d_i-2\)。
这个公式可以等价转化为 \(\prod_{i=1}^{n}(d_i-1)! \times \prod_{i=1}^{|V|}(d_{V_i}-1)^{-1}\),于是我们就可以开始树形 dp 了。
因为 \(\prod_{i=1}^{n}(d_i-1)!\) 的系数是所有链都要乘的,所以我们把它提出来放到最后再乘。
树形 dp
显然,现在这个问题已经被转化为了求解带权的每条连接两个叶子链的总和是多少。
我们设计 \(dp_{i,0/1}\) 表示以节点 \(i\) 为根的子树中目前一共有多少种合法子树方案,且当前子树中有没有关键边。
显然,我们可以每次遍历 \(i\) 节点的一个子树之后,在他们的 LCA,即节点 \(i\) 处统计答案,即可不重不漏。
先能写出遍历到当前子树的答案:
- 当连接子树的边是关键边时,此时一定满足统计进答案的标准,那么 \(res=res+(dp_{v,0}+dp_{v,1})\times(dp_{i,0}+dp_{i,1})\),可以通过把他们乘开来理解这个式子。
- 当连接子树的边是关键边时,此时不一定满足统计进答案的标准,那么就要让前面遍历的子树或者当前子树中至少存在一条关键边,则 \(res=res+dp_{i,0}\times dp_{v,1}+dp_{i,1}\times dp_{v,1}+dp_{i,1}\times dp_{v,0}\)。
接下来考虑合并子树的 \(dp\) 值:
- 当连接的子树是关键边时,此时以 \(i\) 为根的子树内一定含有关键边,那么只转移一个式子即可:
\]
- 当连接的子树不是关键边时,就都能转移:
\]
\]
注意在最后要乘上 \((d_i-1)^{-1}\),包括 \(res\) 与 \(dp\) 值,才能保证求的是这个式子。
如果当前遍历到了叶子节点,那么注意在 dp 完后把 \(dp_{i,0}\) 赋值为 \(1\),才能统计到答案。这也是为什么不能从一个叶子节点开始 dfs 的原因,会漏加这个 \(1\)。
时间复杂度 \(O(Tn)\)。
代码
链式前向星记得开双倍空间!!!
注意特判 \(n=2\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
const ll mod=1e9+7;
const int N=200005;
ll inv[N+100];
int n,m,eu[N],ev[N];
int h[N],e[N],ne[N],idx,id[N],d[N];
ll ans=0,dp[N][2];
bitset<N>sig;
void init()
{
sig.reset();
memset(h,-1,sizeof(h));
memset(d,0,sizeof(d));
idx=0;
ans=0;
}
void add(int u,int v,int x)
{
idx++;
ne[idx]=h[u];
h[u]=idx;
e[idx]=v;
id[idx]=x;
d[u]++;
}
void dfs(int u,int fa)
{
ll res=0;
dp[u][0]=dp[u][1]=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i],x=id[i];
if(v==fa)continue;
dfs(v,u);
if(sig[x])
{
res=(res+(dp[v][1]+dp[v][0])*(dp[u][1]+dp[u][0])%mod)%mod;
dp[u][1]=(dp[u][1]+dp[v][0]+dp[v][1])%mod;
}
else
{
res=(res+dp[u][1]*dp[v][0]%mod+dp[u][1]*dp[v][1]%mod+dp[u][0]*dp[v][1]%mod)%mod;
dp[u][1]=(dp[u][1]+dp[v][1])%mod;
dp[u][0]=(dp[u][0]+dp[v][0])%mod;
}
}
if(d[u]==1)dp[u][0]++;
ans=(ans+res*inv[d[u]-1]%mod)%mod;
dp[u][0]=(dp[u][0]*inv[d[u]-1])%mod;
dp[u][1]=(dp[u][1]*inv[d[u]-1])%mod;
}
void solve()
{
scanf("%d%d",&n,&m);
init();
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&eu[i],&ev[i]);
add(eu[i],ev[i],i);
add(ev[i],eu[i],i);
}
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
sig[x]=1;
}
if(n<=3)
{
printf("1\n");
return;
}
for(int i=1;i<=n;i++)
{
if(d[i]>1)
{
dfs(i,-1);
break;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<d[i];j++)
{
ans=(ans*j)%mod;
}
}
printf("%lld\n",ans);
}
int main()
{
freopen("traverse.in","r",stdin);
freopen("traverse.out","w",stdout);
inv[0]=inv[1]=1;
for(int i=2;i<=N;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
int c,t;
scanf("%d%d",&c,&t);
while(t--)solve();
return 0;
}
Luogu P11363 NOIP2024 树的遍历 题解 [ 紫 ] [ 树形 dp ] [ 组合计数 ] [ adhoc ]的更多相关文章
- 51nod 1812 树的双直径 题解【树形DP】【贪心】
老了-稍微麻烦一点的树形DP都想不到了. 题目描述 给定一棵树,边权是整数 \(c_i\) ,找出两条不相交的链(没有公共点),使得链长的乘积最大(链长定义为这条链上所有边的权值之和,如果这条链只有 ...
- [题解](树形dp/记忆化搜索)luogu_P1040_加分二叉树
树形dp/记忆化搜索 首先可以看出树形dp,因为第一个问题并不需要知道子树的样子, 然而第二个输出前序遍历,必须知道每个子树的根节点,需要在树形dp过程中记录,递归输出 那么如何求最大加分树——根据中 ...
- [NOIP10.3模拟赛]3.w题解--神奇树形DP
题目链接: 咕 闲扯: 这题考场上把子任务都敲满了,5个namespace,400行11k 结果爆0了哈哈,因为写了个假快读只能读入一位数,所以手测数据都过了,交上去全TLE了 把边分成三类:0. 需 ...
- 51Nod - 1405 树的距离之和(树形DP)
1405 树的距离之和 题意 给定一棵无根树,假设它有n个节点,节点编号从1到n,求任意两点之间的距离(最短路径)之和. 分析 树形DP. 首先我们让 \(1\) 为根.要开两个数组 \(up \ d ...
- 【BZOJ2286】消耗战(虚树,DFS序,树形DP)
题意:一棵N个点的树上有若干个关键点,每条边有一个边权,现在要将这些关键点到1的路径全部切断,切断一条边的代价就是边权. 共有M组询问,每组询问有k[i]个关键点,对于每组询问求出完成任务的最小代价. ...
- 【BZOJ3611】大工程(虚树,DFS序,树形DP)
题意:有一棵树,树有边权,有若干次询问,给出一些点,求: 1.这些点互相之间的距离之和 2.点对距离中的最大和最小值 n<=1000000 q<=50000并且保证所有k之和<=2* ...
- [集训队作业2018]蜀道难——TopTree+贪心+树链剖分+链分治+树形DP
题目链接: [集训队作业2018]蜀道难 题目大意:给出一棵$n$个节点的树,要求给每个点赋一个$1\sim n$之内的权值使所有点的权值是$1\sim n$的一个排列,定义一条边的权值为两端点权值差 ...
- Codeforces 348E 树的中心点的性质 / 树形DP / 点分治
题意及思路:http://ydc.blog.uoj.ac/blog/12 在求出树的直径的中心后,以它为根,对于除根以外的所有子树,求出子树中的最大深度,以及多个点的最大深度的lca,因为每个点的最长 ...
- 2020ccpc威海C.Rencontre题解(树形dp)
题目大意:给定一棵带边权树,给三份点的集合U1,U2,U3,求0.5*(E(dis(u1,u2))+E(dis(u1,u3))+E(dis(u2,u3))). 即,我们需要维护两份点的所有距离和.显然 ...
- [题解](树形dp/换根)小x游世界树
2. 小x游世界树 (yggdrasi.pas/c/cpp) [问题描述] 小x得到了一个(不可靠的)小道消息,传说中的神岛阿瓦隆在格陵兰海的某处,据说那里埋藏着亚瑟王的宝藏,这引起了小x的好奇,但当 ...
随机推荐
- Xdebug+Phpstorm远程调试
开发环境: 本地 Windows10 日常开发使用的环境 装有PHPStorm 远程主机 Linux 服务器 装php-fpm/nginx等所有开发依赖环境 本地可以访问远程主机 但远程主机无法访问本 ...
- uni-app项目分包后子包中静态资源丢失
前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验非常棒,公司项目就是主推uni-app. 坑位 随着项目越做越 ...
- 服务拆分之《Dubbo服务跨云通信》
2022年10月开始,公司从阿里请来的架构师将全力推进服务拆分这个计划.实际上这个计划早就提上日程了,只是没有一个带头大哥带着把这个事情搞起来,因为这个系统太庞大了,还非常的复杂,当时就没有哪一个人是 ...
- 2024年1月Java项目开发指南14:关于post中的body和param以及java中的@RequestBody和@RequestParam
在HTTP请求中,POST方法通常用于向服务器发送数据,这些数据可以在请求的body中,也可以在URL的param中.不过,这两者的使用方式和适用场景是不同的. Body:在POST请求中,body主 ...
- 【Vue】学习笔记:Vue组件
文末有我看的这个视频的链接. 目录 组件注册 全局注册 组件基础 组件命名规则 template选项 单项数据流 data选项 局部注册 单独配置组件的选项对象 ES6对象属性简写 组件通信 父组件向 ...
- Kubernetes 服务发现 监控Endpoints
监控 Pod之前的apiserver 实际上就是一种特殊的 Endpoints,现在我们同样来配置一个任务用来专门发现普通类型的 Endpoint,其实就是 Service 关联的 Pod 列表,由于 ...
- java - 正则表达式替换Spring @RequestMapping URL中的@PathVariable值
我在接口(只是为了保存常量)中有Spring MVC URL的定义,例如: String URL_X = "/my-url/{id:[0-9]*}"; String URL_Y = ...
- 【转载】Spring Cloud Gateway-过滤器工厂详解(GatewayFilter Factories)
http://www.imooc.com/article/290816 TIPS 本文基于 Spring Cloud Greenwich SR2 ,理论支持 Spring Cloud Greenwic ...
- CoverageBuilder
public CoverageBuilder(String gitPath, String newBranchName, String oldBranchName, List<String> ...
- 优化大宽表查询性能,揭秘GaussDB(DWS) 谓词列analyze
本文分享自华为云社区<GaussDB(DWS) 谓词列analyze揭秘>,作者:SmithCoder. 1. 前言 适用版本:[9.1.0.100(及以上)] 当前GaussDB(DW ...