[APC001H] Generalized Insertion Sort 题解
将 \(a_i\) 视为放在结点 \(i\) 上面的球;称位置 \(i\) 对应的球为 \(i\),区别于“位置 \(i\) 上面的球为 \(a_i\)”。
考虑树是一条链的时候怎么做(下称链插入方法):此时只需要将这条链上面的球按照编号从上到下排序。这是一个类似插入排序的过程,维护深度最大的的若干个球编号的相对顺序;每次将根上的球插入正在维护的、相对顺序确定球(下称确定球)当中——只需找到确定球中深度最大且编号比它小的球,并将该球插在这个确定球原本的位置;如果不存在这样的确定球,直接把它放在深度最小的确定球的父亲位置。
如何拓展这个做法?
考虑将整个操作的过程分成若干个阶段,在每个阶段我们将一部分的球还原至其原来的位置并在之后的阶段中忽略它们,且称这些球为操作球。问题在于如何选取每一个阶段的操作球,这里给出一种可能的构造(如果一个球满足如下的条件之一,那么将该球设为操作球):
- 该球的目标位置是叶子结点;
- 该球的目标位置有且仅有一个子节点且该子结点对应的球为操作球。
容易得出每个阶段中,操作球构成的子图是若干条互不相交的链。我们实时维护根结点上的球:
- 如果根结点上的球是操作球,找到其目标位置所在的链,对该球使用链插入方法,并将其标记为确定球,下称操作一;
- 如果根结点上的球是非操作球,找到一个编号最大且没有使用过位置使其上面的球为非确定球(可能是操作球或非操作球),将这个球扔到根结点上,并将该位置标记为使用过,下称操作二;
- 当根结点的球成为确定球或者操作二把所有可能的位置都使用过了,就结束这个阶段(因为如果使用了所有的位置,就表示已经尝试过将所有这样的位置上面的球都“顶”上去了,即处理完了所有的操作球),并把已经确定的结点删了,方便之后的各种操作。
操作二中为什么要选择编号最大的位置?因为根据题目我们知道 \(\forall i, p_i<i\),所以 \(N-1,...,0\) 是原树一种 dfs
序的反序,也就是说这么选只会将原来根结点上的球扔到一条链的底端(或一条有确定球的链上深度最小的确定球上方),感性理解就是如果扔到底只可能把我们想要的操作球往上顶,并且它不会影响下面的球(因为它的下面如果有球,那么只会有确定球)。
进行上述操作时,在所有阶段中操作一最多共做 \(N\) 次(每个球只会归位一次),而操作二在每个阶段中在最差情况下会遍历所有球,所以每个阶段中操作二最多做 \(N\) 次。
上述构造总共会进行多少个阶段?我们只需要考虑一条链使用上述方法(每次去掉若干个不相交子链)最多要几条子链构成。考虑重链剖分,根据经典结论一条链最多被划分为 \(\lceil\log_2N\rceil\) 条链,即最多有 \(\lceil\log_2 2000\rceil=11\) 个阶段,所以总操作次数为 \(N+N\lceil\log_2 N\rceil\le 12N\le 24000\),可以通过此题。
参考代码(GNU C++17):
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n; cin>>n;
vector<int> f(n),a(n),t(n),r; f[0]=-1;
vector<vector<int> > g(n);
for(int i=1;i<n;i++)
cin>>f[i],g[f[i]].emplace_back(i);
for(auto &i:a)cin>>i;
auto rotate=[&](int u){
if(int x=a[0];u){
r.emplace_back(u);
while(~u)swap(a[u],x),u=f[u];
}
}; // 进行一次旋转
vector<bool> b(n),l(n),w(n);
while(!b[0]){ // 如果根结点的球没确定就继续构造
for(int i=n-1;~i;i--)
if(!b[i]){
if(g[i].empty())
l[i]=true,t[i]=i;
if(g[i].size()==1&&l[g[i][0]])
l[i]=true,t[i]=t[g[i][0]];
} // 找到所有操作球
// 记得要倒着遍历,从下往上找
int p=n-1;
while(!w[a[0]]){
if(l[a[0]]){
int u=t[a[0]];
while(w[a[u]]&&a[u]>a[0])u=f[u];
w[a[0]]=true,rotate(u);
} // 根结点上是操作球
else{
while(~p&&(b[p]||w[a[p]]))p--;
// 找编号最大且上面有非确定球的位置
if(~p)rotate(p--);
else break; // 没有这样的位置
} // 根结点上是非操作球
}
for(int i=0;i<n;i++)
if(l[i])b[i]=true;
else{
vector<int> v;
for(int j:g[i])
if(!l[j])v.emplace_back(j);
g[i]=v;
} // 一个阶段结束进行删除
}
cout<<r.size()<<endl;
for(int i:r)cout<<i<<'\n';
return 0;
}