题解:AT_apc001_h Generalized Insertion Sort

为数不多能瞎搓出来的构造。

题意:给出一棵以 \(0\) 为根的树和每个点上的值 \(a_i\),每次可以对一个点 \(u\) 进行以下操作:

  • 设从根往下走到 \(u\) 的路径为 \(p_1,p_2,\cdots p_k\),那么令 \(a_{p_i}\leftarrow a_{p_{i+1}}\),这里特殊令 \(p_{k+1}=p_1\)

需要在不多于 \(25000\) 次操作内使得 \(a_i=i\)\(n\le 2000\)

做法:

考虑这个操作在干啥,我们其实可以主要认为这个操作可以帮我们把根节点的数塞到任意一个位置,这样思考会方便一点。

首先考虑特殊情况链怎么做。一个想法是我们维护一个目前已经取出来的元素的序列,我们只需要保持这个序列的相对顺序和链的顺序一样,那么一步步还原就可以得到整个链。同时,这个序列在链上置于最末端,要不然需要还原更大的时候就会往前推一下就爆炸了。

那么考虑将 \(a_0\) 去进行还原,假设我们已经还原的部分值是 \(b_1,b_2\cdots b_k\),我们只先需要找到第一个小于 \(a_0\) 的位置,在这个位置做一次我们的操作,就可以成功构造出新的有序序列。并且这个东西是在链的末尾,所以非常好定位 \(a_0\) 的位置。

但是这个题是对任意的树做,我们需要拓展。一个很基础的想法是,因为叶子在最末端,所以我们肯定需要先还原他们。再贪心一点,我们没有必要只还原叶子,我们完全可以把这个叶子向上的一条没有分叉的链,或者说把叶子的虚树建出来之后到父亲这一部分的链一起处理。先不考虑操作次数的问题,我们先来把算法具体流程做完。

首先先把这些链按叶子编号分类。然后同样考虑我现在有一个 \(a_0\) 该如何插入。如果 \(a_0\) 在我这一轮分的链内,那么就按照对链的操作同样处理,只不过是同时处理多条链罢了;而如果不是,那我们就需要用这个数把别的数都挤上来,那就选一个深度最深的我目前还没确定的点。注意,这里确定的意思为,在前面的轮次中被还原,或者在当前轮次中已经有当前链的元素占据了这个位置。但是这样,如果我存在一条链都不是当前轮次确定的点一直操作就爆炸了,所以我们还需要要求这个点不能被操作过了。因为我们并不在乎那些这一轮不被确定的点具体的编号,所以操作两次同一个点,和操作一次这一个点和他的父亲本质是相同的,所以多次操作是没有意义的。这样做是一个 bfs 序的逆序,所以一定也是能把所有的数都挤上去一次的。

考虑复杂度,首先对于一轮来说,我链上的每个点操作次数是链长的,而其他的点都操作一次,操作次数为 \(O(n)\)。而同时,考虑怎么样尽量构造轮数更多,假设一个点的子树分别需要 \(x_1,x_2,\cdots x_n\) 轮去解决,那么会发现,当只有一个最大值的时候,我显然也只需要这么多轮;当我有两个最大值的时候,我需要的轮数是最大值加一,所以最差的情况也就是一个满二叉树的情况,轮数是 \(O(\log n)\) 的,总共操作次数是 \(O(n\log n)\) 的,可以通过此题。

因为我写的时候思路有点混乱,导致代码可读性可能不是很高/kel。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2005;
int n, vis[maxn], a[maxn], id[maxn], dep[maxn], use[maxn], f[maxn], cnt, ut[maxn];
vector<int> v[maxn], pos, e[maxn], nw[maxn];
int dfs(int u) {
	if(vis[u])
		return 0;
	int son = 0, p = 0, f = 0;
	for (int i = 0; i < e[u].size(); i++) {
		int v = e[u][i];
		dep[v] = dep[u] + 1;
		int t = dfs(v);
		if(t != 0)
			p = t, son++;
		f |= t == -1;
	}	
	if(f)
		return -1;
	if(son <= 1)
		vis[u] = cnt;
	else
		return -1;
	if(son == 0)
		p = u, pos.push_back(p);
	v[p].push_back(u), id[u] = p;
	return p;
}
vector<int> v1;
void shift(int x) {
	v1.push_back(x);
	int v = a[0];
	vector<int> nw;
	for (int p = x; p; p = f[p])
		nw.push_back(p);
	nw.push_back(0);
	for (int i = nw.size() - 1; i > 0; i--)
		a[nw[i]] = a[nw[i - 1]];
	a[x] = v;
}
int main() {
	cin >> n;
	for (int i = 1; i < n; i++) {
		cin >> f[i];
		e[f[i]].push_back(i);
	}
	for (int i = 0; i < n; i++)
		cin >> a[i];
	while(!vis[0]) {
		cnt++;
		pos.clear();
		dfs(0);
		for (int i = 0; i < n; i++)
			use[i] = 0, ut[i] = 0;
		while(!use[a[0]]) {
		//	cout << "adf" << endl;
			if(vis[a[0]]) {
				int p = 0;
				for (int j = 0; j < nw[id[a[0]]].size(); j++) {
					int t = nw[id[a[0]]].size() - 1 - j;
					if(dep[nw[id[a[0]]][j]] < dep[a[0]])
						p = j + 1;
				}
				int t = nw[id[a[0]]].size() - p;
				use[a[0]] = 1;
				nw[id[a[0]]].insert(p + nw[id[a[0]]].begin(), a[0]);
				shift(v[id[a[0]]][t]);
			}
			else {
				int p = -1;
				for (int i = 1; i < n; i++) 
					if(((vis[i] == cnt || vis[i] == 0) && !use[a[i]] && (p == -1 || p < i) && !ut[i]))
						p = i;
			//	cout << a[0] << endl;
				if(p != -1) {
					ut[p] = 1;
					shift(p);
				}
				else
					break;
			}
//				for (int i = 0; i < n; i++)
//					cout << a[i] << " ";
//				cout << endl;
		}
//		for (int i = 0; i < n; i++)
//			cout << vis[i] << " ";
//		cout << endl;
		
	}
	if(v1.size() > 25000)
		exit(100);
	cout << v1.size() << endl;
	for (int i = 0; i < v1.size(); i++)
		cout << v1[i] << endl;
//	for (int i = 0; i < n; i++)
//		cout << a[i] << " ";
	return 0;
}
/*
10
0 1 0 3 2 4 5 6 8
9 8 7 6 5 4 3 2 1 0
*/
posted @ 2025-10-25 23:23  LUlululu1616  阅读(3)  评论(0)    收藏  举报