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的好奇,但当 ...
随机推荐
- golang之操作kafka
安装第三方包: go get github.com/IBM/sarama 生产者实例: package main import ( "fmt" "github.com/I ...
- MongoDB之常见错误
总结开发中常见的问题与错误 1. Robo 3T连接MongoDB异常: Network error while attempting to run command 'saslStart' on ho ...
- 21stUESTC
数矩形 平面上有 \(n\) 个点,这 \(n\) 个点两两不重合.问这 \(n\) 个点可以组成多少个矩形 请注意:矩形的边不必平行于坐标轴. \(4 ≤ n ≤ 1000\) 保证这些点两两不重合 ...
- Vue.js 文本交替滚动
1.前言 当一段文本需要单行显示,但是又限于容器宽度无法完全展示时,我们需要对其滚动展示,所以就有了这个插件,如图: 2.封装思路 使用js模拟循环滚动的动画,容器宽度固定且超出隐藏,文本元素禁止换行 ...
- ehcarts 实战小计-1
需求 展示未来未来36个月(等分为3个时间范围)的经济效益趋势,3个等分时间区域在趋势图上方常显,不同时间区域之间通过灰色虚线间隔开: 鼠标hover趋势图每个1/3区域,对应区域会有以下3个效果: ...
- oracle中id自增长(转)
首先,你要有一张表! CREATE TABLE example( ID Number(4) NOT NULL PRIMARY KEY, NAME VARCHAR(25), PHONE VARCHAR( ...
- 《JavaScript 模式》读书笔记(7)— 设计模式1
这些模式已经出现了相当长的一段时间,并被证明在许多情况下都非常有用.这也是为什么需要自己熟悉并谈论这些模式的原因. 虽然这些设计模式是与语言和实现方式无关的,并且人们已经对此研究了多年,但都主要是从强 ...
- element-ui el-dialog中套el-dialog被遮罩遮盖的问题
添加 append-to-body 属性 具体见官方文档 入口
- 【软件工程与UML】第1章 笔记和练习题
基本概念 软件 = 程序 + 数据 + 文档 软件的特点 抽象性:软件是一种逻辑实体 可复制性:软件是通过人们智力活动,把知识和技术转化为信息的一种产品. 不会磨损: 依赖性:软件的开发和运行经常收到 ...
- 浅谈 IoT 如何助力制造业企业实现数字化落地
物联网作为新一代信息技术的重要组成部分,正在加速渗透到各行各业,成为经济社会数字化转型的关键支撑.根据中商产业研究院发布的<2022-2027 年中国物联网市场需求预测及发展趋势前瞻报告> ...