CSP2019 D1T3 樹上的數
題目
題目描述
給定一個大小爲 的樹,它共有 個結點與 條邊,結點從 編號。初始時每個結點上都有一個 的數字,且每個 的數字都只在恰好一個結點上出現。
接下來你需要進行恰好 次刪邊操作,每次操作你需要選一條未被刪去的邊,此時這條邊所連接的兩個結點上的數字將會交換,然後這條邊將被刪去。
次操作過後,所有的邊都將被刪去。此時,按數字從小到大的順序,將數字 所在的結點編號依次排列,就得到一個結點編號的排列 。現在請你求出,在最優操作方案下能得到的字典序最小的 。
分析
我們不難有一個貪心的想法:枚舉每個數,將它們分別移動到儘可能小的節點上面去。
考慮把一個數字送到另一個數字上,對於一個點我們一共有三種限制:
- 對於起始點,選中的邊必須先於其他所有邊刪掉;
- 對於中轉點,選中的兩條邊必須順序刪掉;
- 對於結束點,選中的邊必須在最後刪掉。
那麼這樣一來我們將刪的邊的順序排成一排,可以發現我們可以用雙向鏈表來維護。於是我們給每個節點 都建立一個雙向鏈表。
但在計算答案的過程中,一定有一些是連不到一起的,這種情況我們直接把它們看成一堆鏈表就可以了。
於是就可以用兩次 DFS 解決這個問題:
枚舉每個數字,第一次 DFS 找出它能夠到達的最小的節點,第二次 DFS 更新鏈表。
總時間複雜度 。
各種情況的討論還是看代碼吧。。。太複雜了 我不想寫 寫不出來。。。
參考代碼
簡直是分類大討論啊。。。
#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;
}