logo AlgoBeat OnlineJudge
登录 注册

题解:[Algo Beat 008 & WWOI R3] Tree Reconfiguration

作者: Murasame 管理员  ·  发布于 2026-05-27 12:03:10  ·  最后修改于 2026-05-27 12:04:06
已通过
审核员:Murasame 管理员 · 2026-05-27 12:04:06

出题人题解。

Analysis

首先在一棵树上,一个直径受限的点集,必定在某个中心的半径内。

为树上 两点的距离。

  • 为偶数时(),中心是树上的一个节点,记作 ,中心区域
  • 为奇数时(),中心是树上的一条边,记作 ,中心区域

任何一个合法的状态, 个被标记的点一定被至少一个合法的中心区域完整包含,所以在树上移动标记相当于中心区域的移动。

假如我们要把当前状态从中心区域 转换到 ,过程中不能违反条件,唯一的方法是先把 个标记都放在两个中心区域 的交集部分。都在交集时,所有被标记的节点都同时在两个中心区域中,因此完成了从 的转移。

Conclusion

两个相邻的中心区域 可以互相转移的充要条件是 。下面考虑证明。

Proof

必要性

我们把区域 划分成三个部分: 中的任意一个节点与 中的任意一个节点之间的距离必定大于

这意味着,在任何合法状态下,不可能同时有标记点存在于 中。

所以起初点在 中时,如果要让任何一个点进入 中不能有任何标记点。

因此 必须 才能装下这 个点。

充分性

已知 ,所以在 内部一定可以把所有节点推到 里面,所有操作都在 内进行,不会违反条件。所有点都到 之后,就都在 的范围内了,所以 的时候一定可以转移。

Construction

我们把所有中心区域看作一张图的顶点,如果两个中心区域的交集大小 就在它们之间连边。我们把得到的图称为中心图。

然后我们在中心图上搜索,如果起点和终点不连通一定无解,否则一定有解(见上证明),可获得约

我们记这个中心区域的移动路径为

(阶段 1)对于 ,我们要把所有标记放到两个区域的交集中,我们使用以下构造方式:

  • 把当前 视作一棵子树;
  • 不断找这棵子树的叶子节点,如果叶子节点没有标记,直接从子树中删除,否则找到距离它最近的空节点推过去。
  • 处理完该叶子节点后从子树中删除,并更新子树形态。
  • 重复此过程,直到当前子树完全包含于

(阶段 2)最后所有标记节点都在 里面的时候,还要调整到最终答案 ,我们使用以下构造方式:

  • 把当前 视作一棵子树;
  • 不断找这棵子树的叶子节点 ,如果叶子节点标记情况与 相同,直接从子树中删除。否则:
    • 如果 有标记,目标没有,把 移到最近的空节点。
    • 如果 没有标记,目标有,就把最近的有标记节点移到 的当前位置。
  • 重复此过程,直到标记节点集合

Number of Operations

我们把中心区域路径展开成一条在树上不重复经过中心的简单路径。记所有过程中出现过的点集并为 。显然 是原树的一棵连通子树,且

对于 中的一条边 ,删去它后得到两个连通块,任选其中一个记为

在上述构造中,一旦某个连通块与当前维护的子树断开,它之后就不会再被使用。因此同一条边上的标记移动方向不会反复改变。于是经过边 的标记数量,等于初始和最终在 中的标记数量差的绝对值,即

又因为 大小相同,所以这个值不超过两侧点数的较小值,即

因此总操作次数最多为 ,其中 的边集。

的一个重心作为根。根据重心的性质,每个儿子子树大小都不超过 。对于一条父亲到儿子的边,它对上式的贡献正好等于儿子子树大小。因此上式等于所有节点到重心的距离之和。

设重心的若干个儿子子树大小为 ,则

对于一棵大小为 的儿子子树,它内部节点到重心的距离之和在退化成一条链取到最大值:

,因此:

由于 ,所以操作次数

Code

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i(a);i<=(b);++i)
#define req(i,a,b) for(int i(a);i>=(b);--i)
using namespace std;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<23,stdin),p1==p2)?EOF:*p1++)
template<typename TP> inline TP read(TP &num)
{
	TP x=0;
	int f=0;
	char ch=getchar();
	while(ch<48||ch>57) f|=ch=='-',ch=getchar();
	while(48<=ch&&ch<=57) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return num=f?-x:x;
}
template<typename ...Args> inline void read(Args &...args)
{
	(read(args),...);
}
template<typename TP> inline void write(TP x)
{
	(x<0)?(putchar('-'),x=-x):0;
	(x>9)?(write(x/10),0):0;
	putchar((x%10)^48);
}
template<typename TP> inline void writeln(TP x)
{
	write<TP>(x);
	puts("");
}
signed main()
{
	int n,k,d; read(n,k,d);
	vector<pair<int,int>> e(n-1);
	vector<vector<int>> g(n+1);
	for(auto &[u,v]:e) read(u,v),g[u].push_back(v),g[v].push_back(u);
	vector<int> S(k),T(k);
	for(auto &x:S) read(x);
	for(auto &x:T) read(x);
	vector dis(n+1,vector<int>(n+1,1e18));
	rep(i,1,n)
	{
		queue<int> q; q.push(i),dis[i][i]=0;
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(auto v:g[u]) if(dis[i][v]>dis[i][u]+1) dis[i][v]=dis[i][u]+1,q.push(v);
		}
	}
	int rad=d/2,cnt=d&1?n-1:n;
	vector cen(cnt,vector<int>()); vector in(cnt,vector<bool>(n+1));
	rep(i,0,cnt-1)
	{
		if(d&1)
		{
			auto [u,v]=e[i];
			rep(x,1,n) if(min(dis[u][x],dis[v][x])<=rad) cen[i].push_back(x),in[i][x]=1;
		}
		else rep(x,1,n) if(dis[i+1][x]<=rad) cen[i].push_back(x),in[i][x]=1;
	}
	vector cg(cnt,vector<int>());
	if(d&1)
	{
		vector<bool> ok(n+1);
		rep(v,1,n) ok[v]=count_if(dis[v].begin()+1,dis[v].end(),[&](int x){return x<=rad;})>=k;
		rep(i,0,n-2) rep(j,i+1,n-2)
		{
			auto [u1,v1]=e[i]; auto [u2,v2]=e[j];
			if((u1==u2&&ok[u1])||(u1==v2&&ok[u1])||(v1==u2&&ok[v1])||(v1==v2&&ok[v1])) cg[i].push_back(j),cg[j].push_back(i);
		}
	}
	else rep(i,0,n-2)
	{
		auto [u,v]=e[i];
		int cntt=0; rep(x,1,n) cntt+=in[u-1][x]&&in[v-1][x];
		if(cntt>=k) cg[u-1].push_back(v-1),cg[v-1].push_back(u-1);
	}
	vector<int> st,ed;
	rep(i,0,cnt-1)
	{
		int oks=1,okt=1;
		for(auto s:S) oks&=in[i][s];
		for(auto t:T) okt&=in[i][t];
		if(oks) st.push_back(i);
		if(okt) ed.push_back(i);
	}
	queue<int> cq; vector<int> cdis(cnt,1e18),fa(cnt,-1);
	for(auto s:st) cq.push(s),cdis[s]=0;
	int edok=-1;
	while(!cq.empty())
	{
		int u=cq.front(); cq.pop();
		if(find(ed.begin(),ed.end(),u)!=ed.end()) {edok=u;break;}
		for(auto v:cg[u]) if(cdis[v]>cdis[u]+1) cdis[v]=cdis[u]+1,fa[v]=u,cq.push(v);
	}
	if(!~edok) return puts("No"),0;
	vector<int> cpath; for(int x=edok;~x;x=fa[x]) cpath.push_back(x);
	reverse(cpath.begin(),cpath.end());
	vector<int> have(n+1),inc(n+1);
	for(auto s:S) have[s]=1;
	for(auto x:cen[cpath[0]]) inc[x]=1;
	vector<pair<int,int>> ans;
	rep(i,0,(int)cpath.size()-2)
	{
		int c1=cpath[i],c2=cpath[i+1];
		vector<bool> ink(n+1);
		rep(x,1,n) ink[x]=in[c1][x]&&in[c2][x];
		vector<int> deg(n+1);
		queue<int> qq;
		rep(x,1,n) if(inc[x])
		{
			for(auto y:g[x]) deg[x]+=inc[y];
			if(deg[x]<=1) qq.push(x);
		}
		while(!qq.empty())
		{
			int x=qq.front(); qq.pop();
			if(!inc[x]||ink[x]) continue;
			if(have[x])
			{
				int tar=-1; queue<int> q_; vector<int> dis_(n+1,1e18),pre(n+1,-1);
				q_.push(x),dis_[x]=0;
				while(!q_.empty())
				{
					int u=q_.front(); q_.pop();
					if(!have[u]) {tar=u;break;}
					for(auto v:g[u]) if(inc[v]&&dis_[v]>dis_[u]+1) dis_[v]=dis_[u]+1,pre[v]=u,q_.push(v);
				}
				vector<int> path; for(int u=tar;~u;u=pre[u]) path.push_back(u);
				reverse(path.begin(),path.end());
				req(j,(int)path.size()-2,0)
				{
					ans.emplace_back(path[j],path[j+1]);
					have[path[j]]=0,have[path[j+1]]=1;
				}
			}
			inc[x]=0;
			for(auto y:g[x]) if(inc[y]&&(--deg[y])==1) qq.push(y);
		}
		for(auto x:cen[c2]) inc[x]=1;
	}
	vector<bool> tar(n+1); for(auto t:T) tar[t]=1;
	vector<int> deg(n+1); queue<int> qq;
	rep(x,1,n) if(inc[x])
	{
		for(auto y:g[x]) deg[x]+=inc[y];
		if(deg[x]<=1) qq.push(x);
	}
	while(!qq.empty())
	{
		int x=qq.front(); qq.pop();
		if(!inc[x]) continue;
		if(tar[x]==have[x]) inc[x]=0;
		else
		{
			bool noneed=have[x]&&!tar[x]; int tary=-1;
			queue<int> q_; vector<int> dis_(n+1,1e18),pre(n+1,-1);
			q_.push(x),dis_[x]=0;
			while(!q_.empty())
			{
				int u=q_.front(); q_.pop();
				if((noneed&&!have[u])||(!noneed&&have[u])) {tary=u;break;}
				for(auto v:g[u]) if(inc[v]&&dis_[v]>dis_[u]+1) dis_[v]=dis_[u]+1,pre[v]=u,q_.push(v);
			}
			vector<int> path; for(int u=tary;~u;u=pre[u]) path.push_back(u);
			reverse(path.begin(),path.end());
			if(noneed) req(j,(int)path.size()-2,0)
			{
				ans.emplace_back(path[j],path[j+1]);
				have[path[j]]=0,have[path[j+1]]=1;
			}
			else req(j,(int)path.size()-1,1)
			{
				ans.emplace_back(path[j],path[j-1]);
				have[path[j]]=0,have[path[j-1]]=1;
			}
			inc[x]=0;
		}
		for(auto y:g[x]) if(inc[y]&&(--deg[y])==1) qq.push(y);
	}
	puts("Yes");
	writeln(ans.size());
	for(auto [u,v]:ans) write(u),putchar(32),writeln(v);
	return 0;
}

暂无评论

登录 后即可评论。