P11363 [NOIP2024] 树的遍历
P11363 [NOIP2024] 树的遍历
题意
给你一个 \(n\) 个点的树,定义边与边相邻当且仅当它们有一个公共端点。一条合法 dfs 路径从某条边出发,如果存在未访问的相邻的边,则必须选择其中一条边走,否则回溯。
给你 \(k\) 个关键边,问以这些关键边为起点可以生成多少不同构的有标号无向边 dfs 树。
\(n \le 10^5\)。
思路
参考 @喵仔牛奶 的做法。
我们给边重新编号,边 \(u\) 表示点 \(u\) 连向父亲的那条边。假设原树以 \(1\) 为根,那么编号 \(\in[2,n]\)。由题目定义,\(u\) 与它的父亲 \(fa_u\),它的兄弟 \(bro_u\) 和它的儿子 \(son_u\) 相邻。以下直接写编号的都是指边,不是点。
当 \(k=1\) 时是好解决的,就是 \(\prod_{i=1}^n (d_i-1)!\)。其中 \(d_i\) 是点 \(i\) 的度数。
对于每个点,它所有边的访问顺序是一个排列,而当我们访问到点 \(u\) 的某条连边时,一定是由某条边过来的,那么由前面的操作我们钦定了这个排列的第一条边。然后点 \(u\) 可以自由决定其他边的访问顺序,所以就是 \(d_u-1\) 的连乘了。
当 \(k>1\) 时,问题是 \(k\) 个点出发可以生成多少不同构的树,也即有多少不同构的树可以以 \(k\) 个点其中一些点出发生成。容易想到容斥,但是首先要发掘更多性质。
我们想知道每个点的连边的排列和 dfs 树的关系。显然 dfs 树可能有同构,因此不是双射。
\(p_i\) 遍历完它的子树之后就会走到 \(p_{i+1}\)。因此 \(p_i\) 和 \(p_{i+1}\) 一定有连边!所以排列对 dfs 树上的一条链。
显然 reverse 排列不会改变 dfs 树。
那么我们可以容斥,计算有多少种合法的定排列头(定起点方向)的方案,然后如果某个点存在两个定起点方向的方案,它们成为排列头时的排列互为 reverse 的排列,dfs 树就重构了,需要减掉。计算起点边属于 \(k\) 个关键边条件下的方案。
因为每个点对应一个边的排列,所以我们按照点分析每个排列。
显然合法的点的连边的访问的排列顺序对应唯一的合法的 dfs 树,且对应唯一的出发边,但是合法的 dfs 树可以对应多个合法的每个点的连边访问排列顺序。因此考虑由 dfs 树推到哪些排列顺序然后推到哪些边可以做起点。
排列顺序只要一直往排列的第一条边的方向走就可以到达起点边了。换句话说,按照每个点的排列的最后一条边的方向画箭头,就可以得到以起点边为“根”的外向树。根必须是特殊边。
相当于要给每个排列选择两个端点,使得向着任意一个端点定向后是一棵外向树,且树根是特殊边。求排列方案数。
假如已经钦定了集合 \(S\) 的边可以作为树根,求出方案数。然后乘上 \((-1)^{|S|+1}\) 的容斥系数。
若边 \(x \in S\),\(x\) 将原树分成两部分,这两部分的点的排列的一个端点方向必须向 \(x\)。
因此所有 \(x \in S\) 一定在一条链上。
在这条链上的点(不含端点)的贡献是 \((d_u-2)!\),其他点是 \((d_u-1)!\)。可以变形成:
前半是简单的,我们要计数后半部分。
进行树形 DP。按照点 DP。DP 的时候带上容斥系数。
设 \(c_i = \frac{1}{d_u-1}\)。
剩下的,和参考题解一样。
时间复杂度线性。
code
代码是不难写的,性质是很难找的。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
constexpr int N=1e5+7,mod=1e9+7,Max=1e5;
int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }
void _add(int &a,int b) { a=add(a,b); }
int mul(int a,int b) { return 1ll*a*b%mod; }
void _mul(int &a,int b) { a=mul(a,b); }
int testid,t,n,k;
int u,v;
typedef pair<int,int> pii;
#define fi first
#define se second
vector<pii> to[N];
int col[N];
int x;
int fac[N],inv[N];
void _init() {
fac[0]=1;
rep(i,1,Max) fac[i]=mul(fac[i-1],i), inv[i]=(i>1 ? mul(inv[mod%i],(mod-mod/i)) : 1);
}
int f[N];
int ans;
void init() {
rep(i,1,n) to[i].clear();
memset(col,0,sizeof(col));
ans=0;
}
void dfs(int u,int fa,int op) {
int w=inv[to[u].size()-1], s=mod-op;
f[u]=mod-op; _add(ans,op);
for(auto p : to[u]) {
int v=p.se, id=p.fi;
if(v^fa) {
dfs(v,u,col[id]);
_add(ans,mod-mul(w,mul(s,f[v]))), _add(f[u],mul((op ? 0 : f[v]),w)), _add(s,f[v]);
}
}
}
void main() {
sf("%d%d",&testid,&t);
_init();
while(t--) {
sf("%d%d",&n,&k);
init();
rep(i,1,n-1) {
sf("%d%d",&u,&v);
to[u].push_back({i,v});
to[v].push_back({i,u});
}
rep(i,1,k) {
sf("%d",&x);
col[x]=1;
}
dfs(1,0,0);
rep(i,1,n) _mul(ans,fac[to[i].size()-1]);
pf("%d\n",ans);
}
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
wing_heart :: main();
}
本文来自博客园,作者:wing_heart,转载请注明原文链接:https://www.cnblogs.com/wingheart/p/18691292

浙公网安备 33010602011771号