LGP11363 [NOIP 2024] 树的遍历 学习笔记

LGP11363 [NOIP 2024] 树的遍历 学习笔记

Luogu Link

前言

想着找一下自己应该写了的这道题的学习笔记,但是没找到。

畏,召。道!?

题意简述

给定一棵树。定义两边相邻当且仅当其有公共端点。我们建立一个新无向图,原图上每一条边对应新图一个点,新图上两点之间有边当且仅当原图上两边相邻。

新图上有 \(k\) 个关键点,问新图上有多少种 \(\texttt{DFS}\) 树。

说大白话就是你要在边上遍历一棵树。给你 \(k\) 条边作为可用起点,问可以搞出多少种 \(\texttt{DFS}\) 树。

\(n\le 10^5\)

做法解析

记原图上点 \(u\) 的度数为 \(d_u\)

\(k=1\) 时的答案显然是 \(\prod_{i=1}^n (d_i-1)!\)。原因很简单:根据 \(\texttt{DFS}\) 的性质,对于原树上每一个结点,它的邻边们都会在 \(\texttt{DFS}\) 树上形成一段链,而这段链在起点确定的情况下,其链头是唯一确定的。

pVgNQje.png

\(k=2\) 时呢?显然不能直接把 \(k=1\) 的答案乘以 \(2\),而是还得去重。把两条关键边之间的边都抽出来形成一条原图上的链,你发现对于链中间的每个原图上结点 \(u\),当其邻边们在新图上链的链头和链尾都确定为原图上链以 \(u\) 端点的两条边时,这样的方案就可以同时被两条关键边所算到。也就是说我们的答案要减去 \(\prod_{i=1}^n(d_i-1)!\times\prod_{i\in L}(d_i-1)^{-1}\)

pVgNMcD.png

前面那坨东西我们不妨记为 \(A\)。又记 \(P_i=(d_i-1)^{-1}\)。则 \(k=2\) 时的答案最终可以写为 \((2-\prod_{i\in L}P_i)A\)

显然可以用形如 \(ans=\sum_{S}(-1)^{|S|+1}g(S)\) 的钦定关键边边集容斥推广到 \(k=8\)。但是这太蠢了,因为你发现:对于一个关键边边集,除非其中所有边共链,否则不可能有哪个方案可以使得以其中所有边为起点都能得到。

这也意味着,我们只用考虑两两的中间没有其它关键边的关键边对,给它们去重即可。

pVgN1nH.png

可以这么理解:对于一个有 \(k\) 条关键边的方案,其被算了 \(k\) 次,当然要去重 \(k-1\) 次,每个相邻关键边对对应一次去重,正好。

这就是一个 \(\texttt{dfs}\) 的事了。我们用 \(dp_u\) 来记录当前的“线头”系数之和。对于每个点 \(u\) 我们结算 \(\text{lca}\)\(u\) 的线头对。如果 \(u\) 往父亲的边也是关键边就加上父亲作边对二者之一的答案并重置线头为 \(1\),否则 \(dp_u\) 继续乘上 \(P_u\) 的系数。

代码非常简单,这题就这么做完了。

代码实现

找到了。嗯,兑。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=m107;
const int MaxN=1e5+5;
int N,K,X,Y,isk[MaxN];
struct edge{int to,id;};
vector<edge> Tr[MaxN];
void addedge(int u,int v,int id){
    Tr[u].push_back({v,id});
    Tr[v].push_back({u,id});
}
mint facr[MaxN],inv[MaxN];
mint P[MaxN],dp[MaxN],ans1,ans2;
void premwork(int n){
    facr[0]=inv[0]=1;
    for(int i=1;i<=n;i++)facr[i]=facr[i-1]*i;
    for(int i=1;i<=n;i++)inv[i]=mint(1)/i;
}
mint sum[MaxN];
void dfs(int u,int f,int k){
    for(auto [v,id] : Tr[u]){
        if(v==f)continue;dfs(v,u,isk[id]);
        ans2-=dp[u]*dp[v]*P[u],dp[u]+=dp[v];
    }
    if(k)ans2-=dp[u]*P[u],dp[u]=1;
    else dp[u]*=P[u];
}
void befinit(int n,int k){
    for(int i=1;i<=n;i++)Tr[i].clear();
    fill(isk+1,isk+n+1,0);
    for(int i=0;i<=n;i++)dp[i]=0;
    ans1=1,ans2=k;
}
void mian(){
    readis(N,K),befinit(N,K);
    for(int i=1;i<N;i++)readis(X,Y),addedge(X,Y,i);
    for(int i=1;i<=N;i++)P[i]=(mint(1)/(Tr[i].size()-1));
    for(int i=1;i<=K;i++)readi(X),isk[X]=1;
    for(int i=1;i<=N;i++)ans1*=facr[Tr[i].size()-1];
    dfs(1,0,0);writi(miti(ans1*ans2)),puts("");
}
int Tpn,Tcn;
int main(){
    freopen("smp1.in","r",stdin);
    premwork(Ocp5);
    readi(Tpn),readi(Tcn);
    while(Tcn--)mian();
    return 0;
}
posted @ 2025-09-03 19:45  矞龙OrinLoong  阅读(60)  评论(0)    收藏  举报