题目描述

有一棵 $n$ 个节点的树,除了某个特定的点 $v$ 外,其余每个节点上有一个颜色互不相同的雕像。

现在,你需要将这些雕像重复排列,你可以重复以下过程:

  1. 选择一个与 "当前未摆放雕像的点 $v$" 相邻的点 $u$。

  2. 将 $u$ 处的雕像放到 $v$ 处 (这之后 $u$ 处就没有雕像了)。

通常,仅通过上述过程无法将雕像排列成所期望的顺序,因此允许你选择两个不相连的点,并用一条边将其连接。同时,你还要最小化移动的次数。

输入格式

第一行包含一个正整数 $n$ ($2 \leq n \leq 2 \times 10^5$),表示树的点数。

第二行包含 $n$ 个非负整数 $a_i$ ($0 \leq a_i \leq n - 1$),表示点 $i$ 上的雕像的颜色。若 $a_i = 0$,表示此时点 $i$ 没有雕像。保证 $a_1, a_2, \cdots, a_n$ 是 $0, 1, \cdots, n - 1$ 的一个排列

第三行包含 $n$ 个非负整数 $b_i$ ($0 \leq b_i \leq n - 1$),表示期望的点 $i$ 上的雕像颜色,和 $a_i$ 用同样的方式给出。

接下来 $n - 1$ 行,每行两个正整数 $u_i, v_i$ ($1 \leq u_i, v_i \leq n$),描述树上的一条边。

输出格式

输出一行,包含 $1 \sim 3$ 个整数:

如果不需要额外连边就能达到目标,输出 0 t,其中 $t$ 表示最少的移动次数。

如果额外连接一条边后就能达到目标,输出 u v t,其中 $u, v$ ($1 \leq u < v \leq n$) 表示所连边的两个端点,$t$ 表示连边后最少的移动次数。可以证明,在题目的条件下,满足要求的 $u, v$ 是唯一的。

如果连接任何一条边后都无法达到目标,输出一个整数 $-1$。

题解

我们将问题考虑为移动 "$0$ 点" (无雕像的点),因此,向同一条边移动两次等价于什么都没干。

当原图是树时,由树的性质 —— 任意两条边之间路径唯一,因此将 "$0$ 点" 移动到目标位置后剩下的点的雕像排布方案是确定的。

因此只需模拟一遍并与 $b_i$ 比对一下即可得知不连边能否完成。

现在考虑加一条边的情况,即我们得到了一棵基环树

我们考虑将 "$0$ 点" 移到环上,从而考虑环中的操作。

容易发现,对于一棵确定的基环树,当 "$0$ 点" 回到环上时,环外的点是不会发生改变的

因此,如果对于两棵 "$0$ 点" 相同的树,如果能通过加一条边互相转化,那么将它们的共同 "$0$ 点" 移到同一个位置,剩下的点形成的置换是一个轮换 (ps: 循环 (Cycle) 的幂次称为轮换 (Rotation),比如 $\begin{pmatrix} 1 & 2 & 3 & 4 & 5 & 6 \\ 3 & 4 & 5 & 6 & 1 & 2 \end{pmatrix}$)

那么如果我们不将这个共同的 "$0$ 点" 移到同一个位置,那么根据这个环中是否包含 "$0$ 点" 分为两种情况:

  1. 环包含 "$0$ 点"。此时,变换形如下图:

    环包含 0 点

    由于所有雕像的颜色互不相同,因此一个 "非 $0$ 点" 上的雕像颜色改变当且仅当这个点在环上。因此我们只需要将所有颜色改变的点和 $0$ 点并在一起看是否成环即可。

  2. 环不包含 "$0$ 点"。此时,变换形如下图:

    环不包含 0 点

    同样分析可以得到,在环上,除了与 "$0$ 点" 最近的一个点外,其余所有的点上的雕像颜色都会改变。因此我们只需要检验所有颜色改变的点和 "深度最小的点" 的父节点并在一起,看看是否成环即可。

    (ps: 这里用了 "深度最小的点" 的说法,我们是默认这个共同 "$0$ 点" 是根节点,以后都将默认这个事实,这会对我们问题的分析带来方便)

于是我们现在已经完成了是否有解判定,并在有解的情况下找到了所要的边。下面的问题就是求最小值啦。

对于不加边的情况,最小值显然是两个 "$0$ 点" 之间的距离,没的话说的。

因此下面就考虑加边的情况。

由于两个 "$0$ 点" 的初始位置可能不相同,因此为了方便,我们固定其中一个 (比如 $b_i$ 中的) "$0$ 点" 为根节点 $r$,并记剩下一个 (比如 $a_i$ 中的) "$0$ 点" 为 $v$ 点。并分另外两种情况讨论:

  1. 路径 $v \leadsto r$ 和环不相交 (没有公共边)。

    此时的最短路稍微简单些,考虑环中深度最浅的点 $L$ 与 $v$ 的 LCA $w$,然后先将 "$0$ 点" 从 $v \leadsto w \leadsto L$,然后进去绕几圈 (顺时针/逆时针,取较短的那个方向),然后再 $L \leadsto w \leadsto r$。如下图所示:

    路径 v→r 和环不相交
  2. 路径 $v \leadsto r$ 和环相交 (有公共边)。

    此时的最短路会稍稍有些复杂。我们假设路径和环的交集为 $z \leadsto w$ ($dep_z > dep_w$),则将 $v \leadsto w$ 后,考虑以下两种转向:

    而两种转向的圈数通常不相等,需要分别计算,不过它们的和应当等于圈长。整个过程如下图所示:

    路径 v→r 和环相交

以上就是求最小值的全部情况,时间复杂度 $O \left( n \right)$。由于只需要求常数次 LCA,因此求 LCA 部分可以通过暴力实现。

代码

#include <bits/stdc++.h>

typedef long long ll;
const int N = 200054, M = N * 2;

int n, E = 0, root;
int to[M], first[N], next[M];
int a[N], b[N], Lu, Lv, Lt;
int p[N], dep[N];
int diff = 0, buc[N];
int len = 0, cyc[N], in_cyc[N];
int A[N], B[N];

inline ll DD(ll &x, const ll y) {return x > y ? x = y : 0;}
inline int dmax(const int x, const int y) {return dep[x] < dep[y] ? y : x;}

inline void addedge(int u, int v) {
	to[++E] = v, next[E] = first[u], first[u] = E;
	to[++E] = u, next[E] = first[v], first[v] = E;
}

void dfs(int x) {
	int i, y;
	for (i = first[x]; i; i = next[i])
		if ((y = to[i]) != p[x])
			p[y] = x, dep[y] = dep[x] + 1, dfs(y);
}

int LCA(int x, int y) {for (; x != y; dep[x] < dep[y] ? y = p[y] : (x = p[x])); return x;}

inline bool find_endpoint() {
	int i;
	for (i = 1; i < diff && p[buc[i]] == buc[i - 1]; ++i);
	if (i == diff) return Lt = Lu = p[*buc], Lv = buc[diff - 1], true;
	Lu = Lv = Lt = p[*buc];
	for (i = 0; i < diff; ++i) {
		if (p[buc[i]] == Lu) Lu = buc[i];
		else if (p[buc[i]] == Lv) Lv = buc[i];
		else return false;
	}
	return true;
}

int main() {
	int i, j, u, v, w, z, offset; ll D;
	scanf("%d", &n);
	for (i = 1; i <= n; ++i) scanf("%d", a + i);
	for (i = 1; i <= n; ++i) scanf("%d", b + i);
	for (i = 1; i < n; ++i) scanf("%d%d", &u, &v), addedge(u, v);
	dfs( root = std::find(b + 1, b + (n + 1), 0) - b );
	u = v = std::find(a + 1, a + (n + 1), 0) - a;
	for (i = 0; u != root; ++i) std::swap(a[u], a[p[u]]), u = p[u];
	if (!memcmp(a + 1, b + 1, n << 2)) return printf("0 %d\n", i), 0;

	for (i = 1; i <= n; ++i) if (a[i] != b[i]) buc[diff++] = i;
	std::sort(buc, buc + diff, [] (const int x, const int y) {return dep[x] < dep[y];});

	if (!find_endpoint()) return puts("-1"), 0;

	if (Lu > Lv) std::swap(Lu, Lv);
	cyc[len++] = Lt;
	for (i = Lu; i != Lt; i = p[i]) cyc[len++] = i;
	std::reverse(cyc + 1, cyc + len);
	for (i = Lv; i != Lt; i = p[i]) cyc[len++] = i;
	for (i = 0; i < len; ++i) in_cyc[ cyc[i] ] = i;
	assert(len > 2);

	w = LCA(v, Lt);
	if (w == Lt) z = dmax(LCA(v, Lu), LCA(v, Lv));

#define process() \
	for (j = 0, i = Lt; i != root; i = p[i]) buc[j++] = i; \
	for (; j--; std::swap(a[buc[j]], a[p[buc[j]]]), std::swap(b[buc[j]], b[p[buc[j]]])); \
	for (i = 0; i < len; ++i) A[i] = a[cyc[i]], B[i] = b[cyc[i]]; \
	assert(!(*A || *B)); \
	offset = std::find(A + 1, A + len, B[1]) - A; \
	if (offset >= len || memcmp(A + 1, B + (len - offset + 1), (offset - 1) << 2) \
					  || memcmp(A + offset, B + 1, (len - offset) << 2)) return puts("-1"), 0;

	if (w == Lt && w != z) {
		if ((in_cyc[z] + 1) % len != in_cyc[p[z]])
			for (std::reverse(cyc + 1, cyc + len), i = 0; i < len; ++i) in_cyc[ cyc[i] ] = i;
		assert( (in_cyc[z] + 1) % len == in_cyc[p[z]] );
		process();
	 	D = dep[v] + std::min(len * (offset - 1ll), (ll)len * (len - offset) - 2 * (dep[z] - dep[w]));
	} else {
		process();
		D = dep[v] + 2 * (dep[Lt] - dep[w]) + (ll)len * std::min(offset - 1, len - offset);
	}
	printf("%d %d %lld\n", Lu, Lv, D);
	return 0;
}

坑1:(对于路径 $v \leadsto r$ 与环相交的情况) 因为在找环的时候环的存储方向是不定的,因此在最后计算的时候需要统一一个正方向 (比如 $v \leadsto w$ 向)。

坑2:注意输出时需要保证 $u < v$,不要忘记。