[CSP2019] 树上的数

题意

给定一个大小为 n n n 的树,它共有 n n n 个结点与 n − 1 n-1 n1 条边,结点从 1 ∼ n 1\sim n 1n 编号。初始时每个结点上都有一个 1 ∼ n 1\sim n 1n 的数字,且每个 1 ∼ n 1\sim n 1n 的数字都只在恰好一个结点上出现。

接下来你需要进行恰好 n − 1 n-1 n1 次删边操作,每次操作你需要选一条未被删去的边,此时这条边所连接的两个结点上的数字将会交换,然后这条边将被删去。

n − 1 n-1 n1 次操作过后,所有的边都将被删去。此时,按数字从小到大的顺序,将数字 1 ∼ n 1\sim n 1n 所在的结点编号依次排列,就得到一个结点编号的排列 P i P_i Pi。现在请你求出,在最优操作方案下能得到的字典序最小 P i P_i Pi

数据范围: 1 ≤ T ≤ 10 , 1 ≤ n ≤ 2000 1\le T\le 10, 1\le n\le 2000 1T10,1n2000

题解

我们考虑贪心,从小到大枚举 n n n 个数,假设枚举到第 k k k 个数(此时已经确定 p 1 ∼ k − 1 p_{1\sim k-1} p1k1),找到编号最小的节点 u u u,让 k k k u u u,并保证存在一种方案能够使 p 1 ∼ k p_{1\sim k} p1k 合法

接下来我们需要考虑怎么保证这件事情。我们从每条边被删去的时间考虑,考虑下述例子:

如果②去点2,我们能够知道: ( 1 , 4 ) (1,4) (1,4) ( 1 , 3 ) , ( 1 , 4 ) (1,3),(1,4) (1,3),(1,4) 中最先被删的(不然②就去别的地方了比如3),对于 4 的邻接表 { ( 4 , 1 ) , ( 4 , 2 ) , ( 4 , 5 ) } \{(4,1),(4,2),(4,5)\} {(4,1),(4,2),(4,5)} 这三条边, ( 4 , 1 ) (4,1) (4,1) 被删后**马上跟着 ** ( 4 , 2 ) (4,2) (4,2) (不然②就去别的地方了比如5)。如果 2 的邻接表不止 ( 2 , 4 ) (2,4) (2,4) 一个,我们还能确定 ( 2 , 4 ) (2,4) (2,4) 是 2 的邻接表中最先被删的(不然②不可能停留在2)。

那么满足这些条件的所有方案是不是都能让②去点2呢?答案可以发现是肯定的。

形式化地讲,对于数 k k k 去点 u u u 这个事件,假设这条路径是 v 0 = p k , v 1 , v 2 , ⋯   , v m = u v_0=p_k,v_1,v_2,\cdots,v_m=u v0=pk,v1,v2,,vm=u,则充要条件分为三部分:

  • ( p k , v 1 ) (p_k,v_1) (pk,v1) p k p_k pk 的邻接表中最先删的。
  • v i v_i vi 的邻接表中, ( v i − 1 , v i ) (v_{i-1},v_i) (vi1,vi) 被删后马上跟着 ( v i , v i + 1 ) (v_i,v_{i+1}) (vi,vi+1) 被删。
  • ( v m − 1 , u ) (v_{m-1},u) (vm1,u) u u u 的邻接表中最后被删的。

接下来我们着手实现贪心算法,我们重新确定一下:

我们考虑贪心,从小到大枚举 n n n 个数,假设枚举到第 k k k 个数(此时已经确定 p 1 ∼ k − 1 p_{1\sim k-1} p1k1),找到编号最小的节点 u u u,满足 k k k u u u 的充要条件(也就是一堆时间的大小关系)不与 1 ∼ k − 1 1\sim k-1 1k1 的条件的并起冲突(指存在一种构造让这些大小关系都成立)。然后让 k k k u u u

我们对于每一个点 u u u 的邻接表考虑建一个图的模型,将每个边变成点,“ x x x 被删后马上跟着 y y y 被删” 抽象成 x → y x\rightarrow y xy 连一条边。对于某条边是邻接表中最先删的我们把它叫做 st \text{st} st,最后删的叫 ed \text{ed} ed,我们尝试挖掘 p p p 全部确定后这个图模型长什么样。首先必定存在 st , ed \text{st},\text{ed} st,ed,其次由于每条边都要被删,所以这个图模型正好有 deg u − 1 \text{deg}_u-1 degu1 条边。所以这个图一定是 st \text{st} st ed \text{ed} ed 的一条链。 这也是“存在一个构造让邻接表中的所有大小关系都满足”的充要条件。

接下来我们考虑在这个邻接表中只确定了部分大小关系,怎么判断是否存在一种构造满足它们。这等价于我们在图模型上可以加边使得最后图是 st \text{st} st ed \text{ed} ed 的一条链。所以对于这个暂时残缺的图,充要条件是:

  • 每个点入度,出度都最多是 1。
  • st \text{st} st 没有入度, ed \text{ed} ed 没有出度。
  • st \text{st} st ed \text{ed} ed 连通了,则所有边都应该在这个连通块内。

所以若想要加入一条边 ( x , y ) (x,y) (x,y) (还未加)仍然合法,充要条件是

  • x x x 的出度, y y y 的入度都是 0。 x , y x,y x,y 不连通。
  • x x x 不是 ed \text{ed} ed y y y 不是 st \text{st} st
  • x , y x,y x,y 将会使得 st , ed \text{st},\text{ed} st,ed 连通,则所有点都要在这个连通块内。

若钦定一个点 x x x st \text{st} st,充要条件是

  • 暂时没有 st \text{st} st,且 x x x 没有入度。
  • 若有 ed \text{ed} ed st \text{st} st ed \text{ed} ed 连通,则所有点都要在这个连通块内。

若钦定一个点 x x x ed \text{ed} ed,跟上述条件差不多。

接下来考虑复杂度,我们使用并查集来判断充要条件,枚举 k k k O ( n ) O(n) O(n),枚举 u u u O ( n ) O(n) O(n),走这一整条路径是 O ( n ) O(n) O(n),总共 O ( n 3 ) O(n^3) O(n3),但考虑到树上的一些单调性,我们从 u u u 开始向外深搜就能 O ( n ) O(n) O(n) 一次判断每个点的合法性,所以总复杂度 O ( n 2 ) O(n^2) O(n2)

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN = 100005;

int F[MAXN << 1], siz[MAXN], st[MAXN], ed[MAXN], in[MAXN], out[MAXN];
int get(int x) {
	if (F[x] != x) F[x] = get(F[x]);
	return F[x];
}
void merge(int x, int y) {
	x = get(x), y = get(y); if (siz[x] > siz[y]) swap(x, y);
	siz[y] += siz[x], F[x] = y;
}

int T, N, p[MAXN], ans[MAXN], deg[MAXN];
struct node { int v, next; } E[MAXN << 1]; int head[MAXN], Elen = 1;
void add(int u, int v) { ++Elen, E[Elen].v = v, E[Elen].next = head[u], head[u] = Elen, F[Elen] = Elen, siz[Elen] = 1; }

bool ok[MAXN], used[MAXN]; int fa[MAXN], come[MAXN]; 
void dfs(int u, int ff, int com) {
	come[u] = com, fa[u] = ff;
	if (!ff) {
		for (int i = head[u]; i; i = E[i].next) if (!st[u] && !in[i] && (!ed[u] || get(ed[u]) != get(i) || siz[get(ed[u])] == deg[u]))
			dfs(E[i].v, u, i ^ 1);
	} else {
		if (!ed[u] && !out[com] && (!st[u] || get(st[u]) != get(com) || siz[get(st[u])] == deg[u])) ok[u] = 1;
		for (int i = head[u]; i; i = E[i].next) if (E[i].v != ff) {
			if (get(i) != get(com) && !in[i] && !out[com] && i != st[u] && com != ed[u]) {
				if (get(com) == get(st[u]) && get(i) == get(ed[u]) || get(com) == get(ed[u]) && get(i) == get(st[u])) {
					if (siz[get(st[u])] + siz[get(ed[u])] != deg[u]) continue;
				}
				dfs(E[i].v, u, i ^ 1);
			}
		}
	}
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &N); int u, v;
		for (int i = 1; i <= N; ++i) scanf("%d", &p[i]);
		for (int i = 1; i < N; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u), ++deg[u], ++deg[v];
		for (int i = 1; i <= N; ++i) {
			for (int j = 1; j <= N; ++j) ok[j] = 0;
			dfs(p[i], 0, 0);
			for (int j = 1; j <= N; ++j) if (j != p[i] && !used[j] && ok[j]) {
				ans[i] = j, ed[j] = come[j], used[j] = 1;
				int las = come[j] ^ 1;
				u = fa[j];
				while (u != p[i]) {
					merge(come[u], las), ++out[come[u]], ++in[las];
					las = come[u] ^ 1, u = fa[u];
				}
				st[p[i]] = las;
				break;
			}
		}
		for (int i = 1; i <= N; ++i) printf("%d ", ans[i]); putchar('\n');
		for (int i = 1; i <= N; ++i) head[i] = st[i] = ed[i] = deg[i] = used[i] = ans[i] = 0;
		for (int i = 1; i <= Elen; ++i) in[i] = out[i] = 0;
		Elen = 1;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值