*题解:P16905 [CCO 2026] Tree Traversals

题目链接

解析

比较繁琐的做法。

先从条件出发,看看能转化为什么限制。

先来看第二个:

  • 对所有满足 \(1 \le i < j \le N\) 的整数对 \((i, j)\),有 \(d(1, p_i) \le d(1, p_j)\)。

其实是在说,深度小的结点需要排在深度大的结点的前面,更进一步地,其实是要求排列中深度相同结点相邻,且深度总体递增。也就是说,每个深度的所有结点在最终排列中对应着一个固定区间。

然后是第一个:

  • 对所有 \(i = 2, \ldots, N\),有 \(d(p_{i-1}, p_i) \le K\)。

没什么好转化的,就是距离 \(\le K\)。

几个约定:

  • \(dep_i\) 表示点 \(i\) 的深度。
  • \(cnt_i\) 表示深度为 \(i\) 的节点个数。
  • \(S_i\) 表示深度为 \(i\) 的节点集合。

考虑同深度结点内部在什么情况下满足条件一。可以发现无论怎么排列,最终相邻结点距离最大值都等于最远点对距离。记该深度 \(k\) 所有点的 LCA 为 \(x\),那么最远点对距离 \(dis_{k} = 2k - 2dep_x\)。所以,如果 \(\max dis > K\) 则无解。否则内部可以任意排列。

考虑排列中相邻两深度交界处的两结点在什么情况下满足条件一。记这两点中深度为 \(k\) 的为点 \(u\),深度为 \(k + 1\) 的为点 \(v\),那么其满足条件一当且仅当 \(d(u,v) \le K\)。考虑贡献,记 \(f_i\) 表示考虑到 \(i\) 且以 \(i\) 作为其深度区间的开头的合法排列数,\(x\) 为满足 \(d(u,v) \le K\) 的 \(u\) 个数,则 \(f_v = \sum_{w\in S_k} f_w \cdot(\lvert S_k\rvert - 2)! \cdot (x - [d(w,v)\le K])\)。特别地,若 \(\lvert S_k\rvert = 1\),记其中唯一元素为 \(w\),有 \(f_v = f_w\)。

接下来对 \(w\in S_k\) 进行分类讨论,对于满足 \(d(w,v)\le K\) 的所有 \(w\),对于这些 \(w\) 的 LCA,其子树内所有深度为 \(dep_w\) 的点恰好为所有的 \(w\),而子树内每一层 BFS 序连续。故可以倍增祖先找到目标 LCA,进而二分得到满足条件的 \(w\) 的 BFS 序左右端点,令 \(f\) 以 BFS 序为下标,前缀和优化统计即可。

然而这样时间复杂度是 \(O(nq \log n)\) 的,不够优秀,瓶颈在于对于每个不同的 \(K\) 都需要求满足 \(d(w,v)\le K\) 的所有 \(w\)。考虑小于等于限制的特性,若 \(\max_{dep_i = dep_j - 1} d(i,j) \le K\) 则应当是随意排列。而对于深度 \(k\),\(\max_{dep_v = k} d(w,v)\) 正好为深度为 \(k - 1\) 的最远点对距离 \(+ 1\),即 \(dis_{k - 1} + 1\),对每个深度取 \(\max\),原式变为 \(\max dis \le K - 1\)。又因为 \(\max dis > K\) 时无解,所以只用对 \(\max dis = K\) 的情况单独计算。

至此,时间复杂度降为 \(O(n\log n)\)。

想到了的地方

\(f_v = \sum_{w\in S_k} f_w \cdot(\lvert S_k\rvert - 2)! \cdot (x - [d(w,v)\le K])\)

对于满足 \(d(w,v)\le K\) 的所有 \(w\),对于这些 \(w\) 的 LCA,其子树内所有深度为 \(dep_w\) 的点恰好为所有的 \(w\)。

没想到的地方

对 \(w\in S_k\) 进行分类讨论。

将对每个 \(K\) 单独求满足 \(d(w,v)\le K\) 的所有 \(w\) 转化为只需求 \(\max dis = K\) 的情况。

没想到的原因

首先有一部分是因为在模拟赛上看到的这题,主要奔着部分分去打。

就算认真想正解我觉得我也想不到只用对 \(\max dis = K\) 的情况单独计算。缺乏对已有信息的整合。看到无解情况没有想到去讨论平凡解的情况。

代码

前面的注释记录了我模拟赛时的思路。

/*
先放完一层,再放下一层

层内定完开头结尾随意排列

若当前层最远点对距离 > K 则无解,因为没法找中转点 

这样 K 只能限制每一层的开头结尾

f[i] 表示 i 所在层以 i 开头,未统计 i 层内部排列的方案数  
枚举上一层 d 的开头 i 与这一层开头 j,要求的就是距离 j 不超过 K 的第 d 层结点个数,以及判断 i,j 距离是否不超过 K。 

找到使得 dis + dis - 1 最大且不超过 K 的祖先,统计其内部第 d 层结点个数即可。  

对于 K = 1,除以 1 为根的链以外无解,链的答案为 1 

对于 K = 2,除每层共用父亲外无解,有解答案为 除叶子层外每层结点数量 - 1 的阶乘 相乘 乘上 叶子层结点数量 的阶乘 
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 5e5 + 5,M = 20,S = 3000 + 5,mod = (int)1e9 + 7;
int fa[M][N],dep[N],dfn[N],siz[N];
int fac[N];
int f[N],pre[N];
int bfn[N],rev[N];
int dcnt;
int n,q;
int mxd,mxdis;
vector<int> t[N],nd[N]; 
bool isl;
void dfs(int x,int f){
	dfn[x] = ++dcnt;
	fa[0][x] = f;
	siz[x] = 1;
	dep[x] = dep[f] + 1;
	mxd = max(mxd,dep[x]);
	nd[dep[x]].push_back(x);
	for(int i=1;i<M;i++){
		fa[i][x] = fa[i - 1][fa[i - 1][x]];
	}
	for(int nx : t[x])if(nx != f){
		dfs(nx,x);
		siz[x] += siz[nx];
	}
}

void bfs(int x){
	queue<int> q;
	q.push(x);
	int now = 0;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		bfn[u] = ++now;
		rev[now] = u;
		for(int v : t[u])if(v != fa[0][u]){
			q.push(v);
		}
	}
}
int get(int x,int d,int K){
	for(int i=M - 1;i>=0;i--){
		int v = d - dep[fa[i][x]];
		if(fa[i][x] && v * 2 - 1 <= K){
			x = fa[i][x];
		}
	}
	return x;
}
int lca(int a,int b){
	if(dep[a] > dep[b]) swap(a,b);
	for(int i=M - 1;i>=0;i--){
		if(dep[fa[i][b]] >= dep[a]){
			b = fa[i][b];
		}
	}
	if(a == b){
		return a;	
	}
	for(int i=M - 1;i>=0;i--){
		if(fa[i][a] != fa[i][b]){
			a = fa[i][a];
			b = fa[i][b];
		}
	}
	return fa[0][a];
}
int dis(int a,int b){
	return dep[a] + dep[b] - 2 * dep[lca(a,b)];
}
int res1,res2;
void sol1(){
	res1 = 1;
	for(int i=1;i<=mxd;i++){
		res1 = 1ll * res1 * fac[nd[i].size()] % mod;
	}
}
int getmxd(){
	int res = 0;
	for(int j=1;j<=mxd;j++){
		int l = nd[j][0];
		for(int k : nd[j]){
			l = lca(l,k);
		}
		res = max(res,j * 2 - dep[l] * 2);
	}
	return res;
}
bool in(int x,int y){
	return dfn[x] >= dfn[y] && dfn[x] <= dfn[y] + siz[y] - 1;
}
void sol2(){
	f[1] = pre[1] = 1;
	int now = 1;
	bool flag = true;
	for(int i=2;i<=mxd;i++){
		int nxt = now + nd[i].size();
		for(int j : nd[i]){	
			if(nd[i - 1].size() < 2){
				f[bfn[j]] = f[bfn[fa[0][j]]];
				continue; 
			}		
			int ff = get(j,dep[j],mxdis);
			int l = now - nd[i - 1].size() + 1,r = bfn[fa[0][j]];
			while(l < r){
				int mid = (l + r) >> 1;
				if(in(rev[mid],ff)){
					r = mid;
				}else{
					l = mid + 1;
				}
			}
			int lpos = l;
			l = bfn[fa[0][j]],r = now;
			while(l < r){
				int mid = (l + r + 1) >> 1;
				if(in(rev[mid],ff)){
					l = mid;
				}else{
					r = mid - 1;
				}
			}
			int rpos = l;
			int cntn = rpos - lpos + 1;
			f[bfn[j]] = 1ll * (pre[rpos] + mod - pre[lpos - 1]) % mod * (cntn - 1) % mod * fac[(int)nd[i - 1].size() - 2] % mod;
			f[bfn[j]] = (f[bfn[j]] + 
				   ((1ll * pre[now] - pre[rpos] + pre[lpos - 1] - pre[now - nd[i - 1].size()]) % mod + mod) % mod 
					* cntn % mod 
					* fac[(int)nd[i - 1].size() - 2] % mod) % mod;			
		}
		for(int j=now + 1;j <= now + nd[i].size();j++){
			pre[j] = (pre[j - 1] + f[j]) % mod;
		}
		now = nxt;
	}
	for(int j : nd[mxd]){
		res2 = (res2 + 1ll * f[bfn[j]] * fac[(int)nd[mxd].size() - 1] % mod) % mod;
	}
	for(int i=1;i<=n;i++){
		f[i] = 0;
		pre[i] = 0;
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("ex_perm6.in","r",stdin);
//	freopen("perm.in","r",stdin);
//	freopen("err.txt","w",stderr); 
//	freopen("perm.out","w",stdout);
	fac[0] = 1;
	for(int i=1;i<N;i++){
		fac[i] = 1ll * fac[i - 1] * i % mod;
	}
	int T;
	cin>>T;
	while(T--){
		mxd = 0;
		cin>>n>>q;
		for(int i=1;i<n;i++){
			int u,v;
			cin>>u>>v;
			t[u].push_back(v);
			t[v].push_back(u);
		}
		dcnt = 0;
		dfs(1,0);
		bfs(1);
		mxdis = getmxd();
		res1 = res2 = 0;
		sol1();
		sol2(); 
		bool flag1 = true;
		while(q--){
			int k;
			cin>>k;
			if(k < mxdis){
				cout<<0<<" ";
			}else if(k > mxdis){
				cout<<res1<<" ";
			}else{
				cout<<res2<<" ";
			}
		}
		cout<<'\n';
		for(int i=1;i<=n;i++){
			t[i].clear();
			nd[i].clear();
		}
	}
	return 0;
}
posted @ 2026-09-25 00:43  yutar  阅读(17)  评论(0)    收藏  举报