P15247 [WC2026] 画树 题解

题目链接:P15247 [WC2026] 画树#17209. 画树

太恐怖了,第一点( $ k_{min} $ 的大小)感觉就很难想到。

下面记初始树为 $ S $,结束时要求的树是 $ T $。

考虑 $ k_{min} $,一个下界是:

\[max([S\not = T], \frac{\sum_{u=1}^n [deg_{S_u} \not = deg_{T_u} \bmod 2]}{2} ) \]

为什么?因为除了最终 笔/橡皮 停留的地点,其他地方都是被 笔/橡皮 走到若干次并走出去同样的次数,使得度数的奇偶性不变,因此至少需要奇偶性不同的点除以 2 对 笔/橡皮。下面的构造将证明能取到这个下界。

首先是一个转化,我们将 笔/橡皮 从同一个地点出发,最终到任意不同点的过程倒过来,即每一个 笔/橡皮 都任选一个点出发,最终和其配对的 笔/橡皮 到达同一个点,这个是显然等价的,而且让操作变宽松了很多。

考虑我们算法的流程应该是:将 $ T $ 的每个点的度数奇偶性变成和 $ S $ 的对应节点一样 -> 将 $ T $ 变成一个标准树 -> 将标准树变成 $ S $

下文中的根节点为点 $ 1 $。

1.奇偶调整

首先我们找出所有 $ deg_{S_u} \not = deg_{T_u} $ 的节点,在这些点上面会放 笔/橡皮 中的一个。此时只要每个 笔/橡皮 移出去就已经满足了要求,现在的目标是将两个我们考虑将其任意两两配对,设点 $ u, v $ 为一对 笔/橡皮。

1.1. $ u, v $ 存在至少一个不为叶子结点

由于对称性不妨设 $ u $ 为那个不为叶子节点的点(因为可以任意交换这一对点的 笔/橡皮)那么显然可以找到一个点 $ w $,使得 $ w $ 为 $ u $ 的儿子,并且 $ w $ 不为 $ v $ 的祖先(因为 $ u $ 一定有不足一个子树)。那么让点 $ u $ 上方的是 橡皮,点 $ v $ 上方的是 笔,让 $ u, v $ 均移动到 $ w $ 即可。

1.2. $ u, v $ 都是叶子节点

可以让 橡皮 每次都移动到相邻节点(父亲节点),铅笔移动到橡皮原来在的节点,直到变成 1.1。

但是,在整棵树时一条链,$ u, v $ 恰好是链的两个端点的时候,这种操作是没用的。此时我们发现一定还有另一对 $ u', v' $ 可以操作,优先操作另一对即可(为什么?如果没有另一对可以操作的点,那么显然有 $ u, v $ 两点的度数奇偶性都是奇数,而其他点都是偶数,那么这一对 笔/橡皮 移动后显然这两个点的度数变为偶数了,于是整个图有 $ 0 $ 个奇度数点,而因为树显然拥有叶子所以是不可能的)

2.结构调整

首先我们定义三个基本操作:

  1. 移动:对于在同一个节点上的一对 笔/橡皮,可以同时让他们移动到另一个节点上,这种操作不改变树的形态。

  2. 压缩:对于点 $ u, v $,其当前的父亲为 $ x $,我们想将点 $ u, v $ 的父亲改为 $ x $ 的一个祖先 $ f $,方法是先将一对 笔/橡皮 移动到点 $ u $,然后笔的移动轨迹是 $ u\to f\to v $,橡皮的移动轨迹为 $ u\to x\to v $。(因为这个操作比较像路径压缩因此得到这个名字)

3.交换:对于点 $ u, v $,其父亲分别为 $ x, y $,我们想将 $ u, v $ 以及其各自下方的子树交换,方法是先将一对 笔/橡皮 移动到点 $ u $,然后笔的移动轨迹是 $ u \to y \to x \to v $ ,橡皮的移动轨迹为 $ u\to x\to y\to v $。

容易发现每种操作操作完后这对 笔/橡皮 仍在一起,因此正着做和反着做没区别,所以只需要找出将固定节点数和每个节点的度数奇偶性后一定能操作成的一棵“标准树”即可。

然后我们可以用以下操作将一棵树变成一棵标准树:

先把原树变成根节点挂着若干条链,具体来说我们检查每个非根节点 $ x $,如果它有至少两个儿子则将两个儿子压缩到根节点,重复直到除了根节点每个点最多只有一个儿子,那么显然是根节点挂着若干条链的形态

然后我们可以使用交换,先选择一条长度大于 1 的链,然后每次用一个节点将另外一整条链接到这条链下面来,这样最终会得到一个蒲公英(一个菊花,但是其中一个子节点拖着一条链)

然后我们使用插入排序,每次可以翻转蒲公英的链的一段(对于链 a-b-...-c-d,想翻转 b-...-c 这一段只用交换,将 $ b $ 连到 $ d $,将 $ c $ 连到 $ a $ 即可,这里的交换可能需要改一下,先把这对 笔/橡皮 移动到 $ a $,然后 笔 的移动轨迹是 $ a\to c\to b\to d $,橡皮 的移动轨迹是 $ a\to b\to c\to d $),所以要将链上的某个数插入到某个位置只用 2 次操作(假设链是 A-x-...-z-y-B,要想将 $ y $ 插入到 $ x $ 前面,只用先翻转整个 $ x $ 到 $ y $ 的链,然后再翻转 $ x $ 到 $ z $ 的链即可),目标是将 链中间偶度数的节点按照编号从小到大排序。然后我们只需要将编号最小的奇度数点从蒲公英的菊花部分找到,使用交换操作交换当前蒲公英链的尾端即可对于固定节点数和每个节点的度数奇偶性后“标准树”唯一(当然如果编号最小的奇度数点已经在尾端则不用操作)。

从标准树变成初始树的过程可以先记录初始树如何变成标准树,然后翻转整个操作序列。

操作数感性理解一下瓶颈在插入排序,而这部分是 $ \frac{1}{2} $ 常数的 $ O(n^2) $ 状物,因此实际上操作次数是很松的。

代码:

#include<bits/stdc++.h>
#define pb push_back
#define pii pair<int,int>
using namespace std;
void setting(int k, std::vector<int> p);
void alter(int t, int x, int y);
struct node{
	int id,x,y;
};
struct Graph{
	int n;
	int a[205][205],deg[205];
	bool flag;
	vector<node> ans;
	void init(int _n=0,bool _flag=0)
	{
		n=_n;
		flag=_flag;
		memset(a,0,sizeof(a));
		memset(deg,0,sizeof(deg));
	}
	void add(int u,int v)
	{
		a[u][v]++;
		a[v][u]++;
		deg[u]++;
		deg[v]++;
	}
	void del(int u,int v)
	{
		a[u][v]--;
		a[v][u]--;
		deg[u]--;
		deg[v]--;
	}
	bool check(Graph &T)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(a[i][j]!=T.a[i][j])return 0;
			}
		}
		return 1;
	}
	void move(int sp,int se,int ep,int ee,int id)
	{
		add(sp,ep);
		del(se,ee);
		if(!flag)ans.pb({id,ep,ee});
		else ans.pb({id,se,sp});
	}
	vector<int> getfa(int id)
	{
		vector<int> fa(n+1,-1);
		queue<int> q;
		fa[id]=0;
		q.push(id);
		while(q.size())
		{
			int u=q.front();
			q.pop();
			for(int v=1;v<=n;v++)
			{
				if(a[u][v]&&fa[v]==-1)
				{
					fa[v]=u;
					q.push(v);
				}
			}
		}
		return fa;
	}
	pii getson(int u,int fa)
	{
		int x=0,y=0;
		for(int v=1;v<=n;v++)
		{
			if(!a[u][v]||v==fa)continue;
			if(!x)x=v;
			else
			{
				y=v;
				break;
			}
		}
		return {x,y};
	}
};
struct par{
	int p,e,id;
	void init(int _p=0,int _e=0,int _id=0)
	{
		p=_p;
		e=_e;
		id=_id;
	}
	void move(Graph &g,int x,int y)
	{
		g.move(p,e,x,y,id);
		p=x;
		e=y;
	}
	void go(Graph &g,int x)
	{
		if(p!=x||e!=x)move(g,x,x);
	}
	void compress(Graph &g,int x,int rt,int u,int v)
	{
		go(g,u);
		move(g,rt,x);
		move(g,v,v);
	}
	void swap(Graph &g,int x,int y,int u,int v)
	{
		go(g,u);
		move(g,y,x);
		move(g,x,y);
		move(g,v,v);
	}
};
void maketree(Graph &g,par &t)
{
	int n=g.n;
	for(int i=2;i<=n;i++)
	{
		while(g.deg[i]>2)
		{
			vector<int> fa=g.getfa(1);
			pii ch=g.getson(i,fa[i]);
			t.compress(g,i,1,ch.first,ch.second);
		}
	}
	vector<int> fa=g.getfa(1);
	vector<vector<int> > chain;
	for(int i=2;i<=n;i++)
	{
		if(g.deg[i]!=1||fa[i]==1)continue;
		vector<int> cur;
		for(int j=i;j!=1;j=fa[j])
		{
			cur.pb(j);
		}
		reverse(cur.begin(),cur.end());
		chain.pb(cur);
	}
	if(!chain.size())return ;
	vector<int> ch=chain[0];
	for(int i=1;i<chain.size();i++)
	{
		t.swap(g,ch[ch.size()-2],1,ch.back(),chain[i][0]);
		ch.pop_back();
		for(auto x:chain[i])
		{
			ch.pb(x);
		}
	}
	ch.insert(ch.begin(),1);
	for(int i=1;i<ch.size()-1;i++)
	{
		int pos=0;
		for(int j=i-1;j>=0;j--)
		{
			if(ch[j]<ch[i])
			{
				pos=j;
				break;
			}
		}
		if(pos==i-1)continue;
		t.swap(g,ch[pos+1],ch[i],ch[pos],ch[i+1]);
		reverse(ch.begin()+pos+1,ch.begin()+i+1);
		if(pos<i-2)
		{
			t.swap(g,ch[pos+2],ch[i],ch[pos+1],ch[i+1]);
			reverse(ch.begin()+pos+2,ch.begin()+i+1);
		}
	}
	int mn=0;
	for(int i=2;i<=n;i++)
	{
		if(g.deg[i]==1)
		{
			mn=i;
			break;
		}
	}
	if(mn!=ch.back())
	{
		t.swap(g,ch[ch.size()-2],1,ch.back(),mn);
	}
}
void init(int c,int t)
{
	return ;
}
void paint(int n,vector<int> u,vector<int> v)
{
	Graph S,T;
	S.init(n,0);
	T.init(n,1);
	for(int i=0;i<n-1;i++)
	{
		S.add(u[i],v[i]);
		T.add(u[i+n-1],v[i+n-1]);
	}
	if(S.check(T))
	{
		setting(0,vector<int>());
		return ;
	}
	vector<int> dif;
	for(int i=1;i<=n;i++)
	{
		if((S.deg[i]^T.deg[i])&1)dif.pb(i);
	}
	int k=max(1,(int)dif.size()/2);
	vector<par> rt(k+1);
	if(!dif.size())rt[1]={1,1,1};
	else
	{
		for(int i=1;i<=k;i++)
		{
			rt[i]={dif[2*i-2],dif[2*i-1],i};
		}
	}
	for(int i=0;i<k;i++)
	{
		for(int j=1;j<=k;j++)
		{
			if(rt[j].p==rt[j].e)continue;
			int cnt=0;
			for(int x=1;x<=n;x++)
			{
				cnt+=(T.deg[x]==1);
			}
			if(cnt==2&&T.deg[rt[j].p]==1&&T.deg[rt[j].e]==1)continue;
			while(T.deg[rt[j].e]==1)
			{
				for(int x=1;x<=n;x++)
				{
					if(T.a[rt[j].e][x])
					{
						rt[j].move(T,rt[j].e,x);
						break;
					}
				}
			}
			vector<int> fa=T.getfa(rt[j].p);
			for(int x=1;x<=n;x++)
			{
				if(T.a[rt[j].e][x]&&x!=fa[rt[j].e])
				{
					rt[j].move(T,x,x);
					break;
				}
			}
			break;
		}
	}
	vector<par> lt(k+1);
	lt[1]={1,1,1};
	for(int i=2;i<=k;i++)
	{
		lt[i]={rt[i].p,rt[i].e,i};
	}
	vector<int> st(k);
	for(int i=1;i<=k;i++)
	{
		st[i-1]=lt[i].p;
	}
	maketree(S,lt[1]);
	maketree(T,rt[1]);
	lt[1].go(S,rt[1].p);
	setting(k,st);
	for(int i=0;i<S.ans.size();i++)
	{
		alter(S.ans[i].id,S.ans[i].x,S.ans[i].y); 
	}
	for(int i=T.ans.size()-1;i>=0;i--)
	{
		alter(T.ans[i].id,T.ans[i].x,T.ans[i].y); 
	}
}
posted @ 2026-07-23 00:54  MCYYDS  阅读(2)  评论(0)    收藏  举报