[NOIP2024] 树的遍历 题解

[NOIP2024] 树的遍历 题解


知识点

树形 DP。


题意简述

给定一棵树,并给定 \(k\) 条关键边,以其中任意一条为根,对树的边进行 DFS 遍历建新树,问有多少种新树。


分析

考虑画图理解,发现对于一种新树,能成为它的根节点的只有一条从叶子到叶子的链上的边,因为每个非叶节点周围的新边会排成一条链,再加一条新边就可以变成一个简单环。那么我们上面说的叶子到叶子的链就是非叶节点周围排成的链的两端连起来,其中那个满足从叶子到叶子的链,这种链每种新树只会有一条。

如果将链确定了,我们可以列出式子:

\[\prod_{i=1}^n(d_i-1)!\times \prod_u (d_u - 1)^{-1} \]

其中 \(u\) 指在链上的点,将 \(0^{-1}\) 视为 \(1\)

我们转化并抽象一下题意,就变成了:给定一棵树,每个点有点权,为 \((d_u-1)^{-1}\),每条边有边权,为 \(0/1\),求所有满足链上存在 \(1\) 边的从叶子到叶子的链的点权乘积和。

剩下的情况就是:

  1. \(n=2\),特判。

  2. \(n>2\),考虑 DP,找一个叶节点作为根 \(rt\),设 \(f_{u,0/1}\) 表示从子树中某个叶节点到 \(u\),是否有 \(1\) 边的所有方案点权和。

    然后还可以容斥把 DP 数组压到 \(f_u\) 一维,即只记没有 \(1\) 边的所有方案点权和。


代码

constexpr int N(1e5+10);

bool mark[N];
int ID,Cas,n,k,ans;
int f[N],fa[N],pa[N];
struct CFS {
	int tot,h[N],deg[N];
	struct edge {
		int v,w,nxt;
		edge(int v=0,int w=0,int nxt=-1):v(v),w(w),nxt(nxt) {}
	} e[N<<1];
	
	edge &operator [](int i) {
		return e[i];
	}
	
	int &operator ()(int i) {
		return deg[i];
	}
	
	void Init(int n) {
		RCL(h+1,-1,int,n),RCL(deg+1,0,int,n),tot=0;
	}
	
	void att(int u,int v,int w) {
		e[++tot]=edge(v,w,h[u]),h[u]=tot;
	}
	
	void con(int u,int v,int w) {
		att(u,v,w),att(v,u,w),++deg[u],++deg[v];
	}
	
} g;

void dfs(int u) {
	int sum(mark[pa[u]]?Mod-1:0);
	f[u]=sum,toadd(ans,mark[pa[u]]);
	EDGE(g,i,u,v)if(v^fa[u]) {
		fa[v]=u,pa[v]=g[i].w,dfs(v),toadd(ans,Mod-mul(inv[g(u)-1],mul(sum,f[v])));
		toadd(f[u],mark[pa[u]]?0:mul(f[v],inv[g(u)-1])),toadd(sum,f[v]);
	}
}

int Cmain() {
	/*Input*/
	I(n,k),ans=0;
	/*Init*/
	g.Init(n),RCL(mark+1,0,bool,n-1);
	/*Build*/
	FOR(i,1,n-1) {
		int u,v;
		I(u,v),g.con(u,v,i);
	}
	FOR(i,1,k) {
		int u;
		I(u),mark[u]=1;
	}
	/*Calc*/
	dfs(1);
	FOR(i,1,n)tomul(ans,fac[g(i)-1]);
	/*Output*/
	O(ans,'\n');
	return 0;
}
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	for(Init(),I(ID,Cas); Cas; --Cas)Cmain();
	return 0;
}

反思

  1. 当时一直在焦虑自己连 \(k=1\) 都打不出来,大脑放空,完全没去分析性质。
posted @ 2025-11-17 20:43  Add_Catalyst  阅读(3)  评论(0)    收藏  举报