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]);
}