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的好奇,但当 ...
随机推荐
- Go操作数据库之MySQL
安装mysql驱动: go get -u github.com/go-sql-driver/mysql 初始化模块 go mod init m 执行 go mod tidy 导入包: package ...
- 网站动静加速架构 dcdn+ga 全站加速和全球加速api
背景: 1,公司服务全部在香港 2,所有的服务也都在香港 3,但是我们的客户都在国内 4,那么国内用户访问香港的服务 那么就会存在慢的问题 至于为什么不放到国内,因为我们公司是做nft的.所以你懂得. ...
- C#微信公众号开发
C#微信公众号开发 一> 准备 [开发文档] [微信公众号测试接口] 用自己的微信扫码登陆,然后就可以获取就有了appId 和 appsecret. 二>获取access_token 打开 ...
- ARCGIS 拓扑检查步骤与修正拓扑错误技巧
转自http://xygszsh.blog.163.com/blog/static/19221517201111831348533/,写得没有说多好,贵在详细. 将数据装载如个人地理数据库,用拓扑功能 ...
- 深度学习环境搭建(Windows11)
深度学习环境的搭建(Windows11) 偶然重装了系统,在此记录下环境的恢复 基本深度学习环境的搭建,包括Anaconda+CUDA+cuDNN+Pytorch+TensorRT的安装与配置. ps ...
- 中电金信:GienTech动态|丰收之秋,公司多项目获得荣誉
中电金信微电影<妙"笔"生花>获国资委表彰 近日,国务院国资委在京举行中央企业社会主义核心价值观主题微电影(微视频)展映发布活动.中电金信作品<妙&quo ...
- 聊一聊 C#后台线程 如何阻塞程序退出
一:背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题:后台线程的内部是如何运转的 ? ,犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程 ...
- 【Git】Gie基础操作学习笔记01
获取项目信息 remote可以看做是一个人的电脑,假设有十个人合作,那么就有10个remote对象.为了方便大家同步,我们创建一个叫做origin的remote,大家都和这个origin同步,那么大家 ...
- Java调用与发布Webservice接口(一)
一 准备工作 (一)开发环境 demo以springboot为基础框架,使用到了httpclient.hutool等依赖,详情如下: springboot版本: org.spri ...
- 视频监控推流助手/极低延迟/支持N路批量多线程推流/264和265推流/监控转网页
一.前言说明 搞视频监控开发除了基本的拉流以外,还有个需求是推流,需要将拉到的流重新推流到流媒体服务器,让流媒体服务做转发和负载均衡,这样其他地方只需要问流媒体服务器要视频流即可.为什么拉了又重新推呢 ...