CF1508E Tree Calendar

一、题目

点此看题

二、解法

首先把操作转成人话,也就是第 \(i\) 轮我们选择 \(\tt dfs\)\(i\),把它沿某条路径转到叶子处。我们要思考的是这条路径有什么性质,整个旋转过程又有什么性质?

性质1:整个旋转过程不改变任意节点儿子 \(\tt dfs\) 序的偏序关系。

可以归纳证明,考虑旋转操作之前儿子序列满足 \(a_{s_1}<a_{s_2}...<a_{s_k}\),那么我们找到第一个满足 \(a_u<a_{s_x}\)\(s_x\) 旋转,那么旋转之后满足 \(a_{s_1}...<a_{s_{x-1}}<a_u<a_{s_{x+1}}...<a_k\),可见偏序关系并不改变。

性质2:整个旋转过程可以看成把 \(\tt dfs\) 序变成 \(\tt exit\) 序的过程。

我们可以发现待旋转的 \(\tt dfs\) 序最小值总是出现在根上,首先会把最小值转到叶子上,然后沿着旋转路径回溯之后再进入新的子树,再把次小值转下去,以次类推\(...\)(真的解释不清楚啊)

根据性质 \(1\) 可以发现一个有趣的东西,既然偏序关系不会改变,那么题目其实间接的告诉了你偏序关系,所以我们可以唯一得到一个初始 \(\tt dfs\),问题转化成判断这个初始状态是否能变化到末状态。

为了方便我们找到正在旋转的点 \(x\)(它的 \(\tt dfs\) 序是 \(a_1-1\)),然后撤回它的旋转,这样得到的状态是若干个点已经完成旋转的状态,那么整棵树的 \(\tt dfs\) 性质就没被打乱。然后我们对于每个点判断是否合法:

  • 对于前 \(x-1\) 个完成旋转的点,判断它们是否与 \(\tt exit\) 序的位置相对应。
  • 对于第 \(x\) 个点,旋转前它是对应 \(\tt exit\) 节点的祖先。
  • 对于 \(x\) 以后的点,判断整体 \(\tt dfs\) 序的偏序关系是否对应。

旋转此时就是暴力撤回的次数 \(+\)\(x-1\) 个点的 \(\tt exit\) 序深度。

时间复杂度 \(O(n)\),如果 \(a_1=1\) 相当于没有旋转直接判等即可。

三、总结

初末状态的题找不变量!

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int M = 300005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cur,cnt,Ind,a[M],bp[M];long long ans;
int fa[M],np[M],ep[M],ex[M],in[M],dep[M];
vector<int> G[M],g[M];
int cmp(int x,int y)
{
	return a[x]<a[y];
}
void build(int u,int p)
{
	for(auto v:G[u])
	{
		if(v==p) continue;
		dep[v]=dep[u]+1;fa[v]=u;
		build(v,u);
		g[u].push_back(v);
	}
	sort(g[u].begin(),g[u].end(),cmp);
}
void dfs1(int u)//get exist order
{
	for(auto v:g[u]) dfs1(v);
	ex[u]=++cnt;ep[cnt]=u;
}
void dfs2(int u)
{
	if(a[u]<cur) return ;
	in[u]=++Ind;bp[Ind]=u;
	for(auto v:g[u]) dfs2(v);
}
void dfs3(int u)//get dfn order
{
	in[u]=++Ind;
	for(auto v:g[u]) dfs3(v);
}
void rotate(int u)//rotate it to root
{
	if(u==1) return ;
	ans++;
	swap(a[u],a[fa[u]]);
	rotate(fa[u]);
}
int find(int u,int v)
{
	if(u==v) return 1;
	if(u==1) return 0;
	return find(fa[u],v);
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),np[a[i]]=i;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	build(1,0);
	dfs1(1);
	if(a[1]==1)//haven't move
	{
		dfs3(1);
		for(int i=1;i<=n;i++)
			if(in[i]!=a[i])
			{
				puts("NO");
				return 0;
			}
		puts("YES\n0");
		for(int i=1;i<=n;i++)
			printf("%d ",a[i]);
		puts("");
		return 0;
	}
	cur=a[1]-1;
	if(!find(ep[cur],np[cur]))
	{
		puts("NO");
		return 0;
	}
	rotate(np[cur]);
	for(int i=1;i<=n;i++) np[a[i]]=i;
	for(int i=1;i<cur;i++)
		if(np[i]!=ep[i])
		{
			puts("NO");
			return 0;
		}
	Ind=cur-1;
	dfs2(1);
	for(int i=cur;i<=n;i++)
		if(bp[i]!=np[i])
		{
			puts("NO");
			return 0;
		}
	puts("YES");
	for(int i=1;i<cur;i++)
		ans+=dep[np[i]];
	printf("%lld\n",ans);
	Ind=0;
	dfs3(1);
	for(int i=1;i<=n;i++)
		printf("%d ",in[i]);
}
posted @ 2021-09-07 17:11  C202044zxy  阅读(65)  评论(0编辑  收藏  举报