树上的数 题解
2023-08-31 11:18:47
发布于:广东
41阅读
0回复
0点赞
分析
我们不难有一个贪心的想法:枚举每个数,将它们分别移动到尽可能小的节点上面去。
考虑把一个数字送到另一个数字上,对于一个点我们一共有三种限制:
- 对于起始点,选中的边必须先于其他所有边删掉;
- 对于中转点,选中的两条边必须顺序删掉;
- 对于结束点,选中的边必须在最后删掉。
那么这样一来我们将删的边的顺序排成一排,可以发现我们可以用双向链表来维护。于是我们给每个节点 u uu 都建立一个双向链表。
但在计算答案的过程中,一定有一些是连不到一起的,这种情况我们直接把它们看成一堆链表就可以了。
于是就可以用两次 DFS 解决这个问题:
枚举每个数字,第一次 DFS 找出它能够到达的最小的节点,第二次 DFS 更新链表。
总时间复杂度
各种情况的讨论还是看代码吧。。。太复杂了 我不想写 写不出来。。。
AC代码
简直是分类大讨论啊。。。
#include <cstdio>
#include <algorithm>
using namespace std;
const int Maxn = 2000;
struct Edge {
int to;
Edge *nxt;
};
Edge pool[Maxn * 2 + 5];
Edge *G[Maxn + 5], *ecnt;
inline void addedge(int u, int v) {
Edge *p = ++ecnt;
p->to = v, p->nxt = G[u];
G[u] = p;
}
int N, num[Maxn + 5];
int pre[Maxn + 5][Maxn + 5], nxt[Maxn + 5][Maxn + 5];//链表的前后指针
int head[Maxn + 5][Maxn + 5], tail[Maxn + 5][Maxn + 5];//链表的头节点和尾节点
int len[Maxn + 5][Maxn + 5];//链表的大小
int deg[Maxn + 5];
inline void clear() {
ecnt = &pool[0];
for(int i = 1; i <= N; i++)
G[i] = NULL;
for(int i = 1; i <= N + 1; i++)
for(int j = 1; j <= N + 1; j++)
len[i][j] = pre[i][j] = nxt[i][j] = head[i][j] = tail[i][j] = 0;
for(int i = 1; i <= N; i++)
deg[i] = 0;
}
int minp;
void DFS1(int u, int fa) {//找到对应的最小的节点
int st = head[u][fa], ed = tail[u][fa];
if(fa == N + 1) {//这是起点
for(Edge *p = G[u]; p != NULL; p = p->nxt) {
//枚举这个点上的第一条应该删掉的边
int v = p->to;
int t1 = head[u][v], t2 = tail[u][v];
if(t1 != v || (pre[u][fa] == t2 && len[u][t1] < deg[u]))
continue;
//若这个点 v 已经有了一个起点且并不是它自己
//或者,这个点的链表形成了一个环但总长度无法将所有的边连接在一起
//那么这条边就不能够被第一个选中
DFS1(v, u);
}
} else {
if(fa == ed) {//如果 u 后面并没有选到一条边,那么枚举一个点
if(pre[u][N + 1] == 0 && (nxt[u][N + 1] != st
|| len[u][st] == deg[u])) minp = min(minp, u);
//当 u 能被当做结束点的时候
//首先,这个点的最后一条没有删的边没有指定
//其次,如果这个点的链表的开头会连上结尾,那么长度必须等于 u 的出度
//否则长度随意
for(Edge *p = G[u]; p != NULL; p = p->nxt) {
int v = p->to;
int t1 = head[u][v], t2 = tail[u][v];
if(st == t1 || t1 != v || nxt[u][N + 1] == v)
continue;
//若这两条边 (u, fa) 和 (u, v) 已经在同一个链表上面
//或者,这条边不是一条开始边,即它之前还有其他的边要被删掉
//或者这条边就是一条开始边
//那么 (u, v) 就不能够接在 (u, fa) 后面
if(nxt[u][N + 1] == st && pre[u][N + 1] == t2
&& len[u][st] + len[u][t1] < deg[u]) continue;
//若边 (u, v) 和边 (u, fa) 为应该最先删和最后删的边的尾部和头部
//那么它们的长度加起来必须等于这个点的度数
//否则就是提前成了一个环,是不合法的状态
DFS1(v, u);
}
} else DFS1(nxt[u][fa], u);//否则只能够按照我们之前已经确定了的路径找
}
}
inline void merge(int u, int st, int ed) {
int t1 = head[u][st], t2 = tail[u][ed];
nxt[u][st] = ed, pre[u][ed] = st;
for(int i = t1; i != 0 && i != N + 1; i = nxt[u][i])
head[u][i] = t1, tail[u][i] = t2;
len[u][t1] += len[u][ed];
}//将两个链表连在一起
bool DFS2(int u, int fa) {//更新链表
//当这个函数返回 true 时则表示找到了路径
if(u == minp) {//找到了这个数的结束点
pre[u][N + 1] = fa, nxt[u][fa] = N + 1;
//将它设为最后一条边,并设为最后删掉的边
//注意:我们用 N + 1 来标识最后一条边
return true;
}
int st = head[u][fa], ed = tail[u][fa];
if(fa == N + 1) {
for(Edge *p = G[u]; p != NULL; p = p->nxt) {
int v = p->to;
int t1 = head[u][v], t2 = tail[u][v];
if(t1 != v || (pre[u][N + 1] == t2 && len[u][t1] < deg[u]))
continue;//参照 DFS1 中对应位置上的注释
if(DFS2(v, u)) {//找出正确的路径了
nxt[u][N + 1] = v, pre[u][v] = N + 1;
//将 u 标记为起始点并加入链表
return true;
}
}
} else {//中转点
if(fa == ed) {//如果 u 后面并没有选到一条边,那么枚举一个点
for(Edge *p = G[u]; p != NULL; p = p->nxt) {
int v = p->to;
int t1 = head[u][v], t2 = tail[u][v];
if(st == t1 || t1 != v || nxt[u][N + 1] == v)
continue;
if(nxt[u][N + 1] == st && pre[u][N + 1] == t2
&& len[u][st] + len[u][t1] < deg[u]) continue;
//参考 DFS1 中的对应位置注释
if(DFS2(v, u)) {
merge(u, fa, v);//这时我们应该将两个链表连到一起了
return true;
}
}
} else DFS2(nxt[u][fa], u);//按照既定路径找下去
}
return false;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int _;
scanf("%d", &_);
while(_--) {
scanf("%d", &N);
clear();
for(int i = 1; i <= N; i++)
scanf("%d", &num[i]);
for(int i = 1; i < N; i++) {
int u, v;
scanf("%d %d", &u, &v);
addedge(u, v), addedge(v, u);
++deg[u], ++deg[v];
pre[u][v] = pre[v][u] = nxt[u][v] = nxt[v][u] = 0;
head[u][v] = tail[u][v] = v, head[v][u] = tail[v][u] = u;
len[u][v] = len[v][u] = 1;
}
if(N == 1) {
puts("1");
continue;
}
for(int i = 1; i <= N; i++) {
minp = N + 1;
DFS1(num[i], N + 1);
DFS2(num[i], N + 1);
printf("%d%c", minp, (i == N) ? '\n' : ' ');
}
}
return 0;
}
全部评论 1
👍👍👍👍👍👍👍👍👍👍👍
2024-07-25 来自 北京
0
有帮助,赞一个