Loading

P11363 [NOIP2024] 树的遍历 题解

思路:

从简单情况考虑,所以考虑当 \(k = 1\) 时的答案。可以发现,当我们有了一个关键边之后,到达每个点的边都是确定的,并且,这个点的邻边在新树上会构成一条链,所以除了进入这个点的边以外,其他的邻边可以任意安排顺序,所以这个点的贡献就是 \((deg_u - 1)!\) 。所以总贡献就是:

\[\prod_{u\in T}(deg_u-1)! \]

再看到部分分,考虑 \(k=2\) 的情况,一个直接的想法是将 \(k=1\) 时的答案乘以\(2\),但是这是错的,通过样例2解释你就会发现,这种情况可能会有重复的计数出现。所以考虑能同时满足两个关键点生成的树有什么特点:显然的,这两个关键边之间会形成一条链,自己画一下图就可以发现,链上的每一个点不仅入边确定了,出边也是确定的,这样才能满足这颗树会同时出现在第一个关键边和第二个关建边的贡献内。所以链上的每个点的贡献就为 \((deg_u-2)!\) ,所以令这条链为 \(L\) (不包括链的两个端点)。 所以形式化的,重复的树的数量就为:

\[\prod_{u\in T}(deg_u-1)!\times\prod_{u\in L}(deg_u-1)^{-1} \]

这就促使我们用容斥原理解决这道题了,钦定一个关键边集 \(S\) ,求可以同时以这些关键边为根的新树个数。个数记为 \(g(S)\) ,所以最终答案为:

\[\sum_{S}(-1)^{|S|+1}g(S) \]

那么现在考虑一般情况,可以发现的是,如果以某个点为根有至少3个子树存在钦定关键边,那么重复的数量就为\(0\)。也就是我们容斥中钦定的关键边必然构成原树上的一条链才是有有效贡献的

如果我们尝试去确定这条链,你会发现每一条链的贡献最终形式化的表达就是当 \(k=2\) 时的那个式子。

我们考虑把 \(\prod_{u\in T}(deg_u-1)!\) 这部分提出来最后乘。所以此时就可以记点\(u\)的点权为:\(c_u=(deg_u-1)^{-1}\),所以一条链的贡献就是这条链上的点权之积。定义 \(f_u\) 表示钦定的关键点集构成的链经过 \(u\) ,且恰好其中一个端点在 \(u\) 子树内(包括\(u\))的带权方案数(也就是所有可能的链的方案和)。我们分情况转移(注意转移的时候我们先不考虑容斥系数指数上面多出来的\(+1\),留到最后统计答案的时候处理):

1. 若边 \((u,fa_u)\) 可以作为钦定的关键边,所以转移贡献可以由一下几部分加起来组成:只选 \((u,fa_u)\),只选子树内的链 \(c_u\sum_vf_v\),两者同时选 \(-c_u\sum_vf_v\),所以总贡献就为\(-1\)

2. 若不是上述情况,就只能只选子树内的链,贡献为 \(c_u\sum_vf_v\)

所以形式化的:

\[f_u = \begin{cases} -1,\left(if\ (u,fa_u)\ is\ key \ edge\right)\\ c_u\sum_vf_v\\ \end{cases} \]

现在考虑如何统计答案。

朴素的方法是合并子树,即对于点\(u\),我们考虑枚举其子树,将子树中的方案乘起来,然后合并枚举到的子树继续处理。设 \(s\) 为当前遍历到的子树贡献和,所以形式化的:

\((u,fa_u)\)可以作为关键边,那么先让 \(ans\leftarrow ans+1\) (只选这条边),\(s=-1\)

接着枚举儿子 \(v\)

\[ans\leftarrow ans-c_u\times s\times f_v \]

\[s\leftarrow s+f_v \]

枚举儿子贡献 \(ans\) 的时候是减法是因为我们把容斥系数上面的 \(-1\) 留到了贡献答案的时候来处理。

总时间复杂度 \(O(Tn)\)

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
	while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
	return x * f;
}
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
int n,k,fac[N],inv[N],vis[N],ans,f[N];
std::vector<std::pair<int,int> > G[N];
void init()
{
	fac[0] = 1;	
	for(register int i = 1;i < N;++i) fac[i] = fac[i - 1] * i % MOD;
	inv[0] = inv[1] = 1;
	for(register int i = 2;i < N;++i){
		inv[i] = MOD - 1ll * (MOD / i) * inv[MOD % i] % MOD;
	}
}

void dfs(int u,int fa,int fl)
{
	int s = -fl;
	f[u] = -fl;
	ans += fl;	
	int cost = inv[G[u].size() - 1];
	
	for(auto tmp : G[u])
	{
		int v = tmp.first,id = tmp.second;
		if(v == fa) continue;
		dfs(v,u,vis[id]);
		f[u] = (f[u] + (fl ? 0 : f[v]) * cost % MOD + MOD) % MOD;
		ans = (ans - ((cost * s % MOD) * f[v] % MOD) + MOD) % MOD;
		s = (s + f[v] + MOD) % MOD;
	}
}

signed main()
{
	int c,T;
	c = read();
	T = read();
	init(); 
	while(T--)
	{
		n = read();
		k = read();
		for(int i = 1;i < n;++i)
		{
			int u,v;
			u = read();
			v = read();
			G[u].emplace_back(v,i);
			G[v].emplace_back(u,i);
		}
		
		for(int i = 1;i <= k;++i)
		{
			int id;
			id = read();
			vis[id] = 1;
		}
		
		dfs(1,0,0);
		
		for(int i = 1;i <= n;++i) ans = (ans * fac[G[i].size() - 1]) % MOD;
		std::cout << ans << '\n';
		for(int i = 1;i <= n;++i){
			G[i].clear();
			vis[i] = 0;
		}
		ans = 0; 
	}
	return 0;
}
posted @ 2025-03-05 08:26  AxB_Thomas  阅读(149)  评论(0)    收藏  举报
Title