【CSP-S2019】D1T3 樹上的數

CSP2019 D1T3 樹上的數

題目

題目描述

給定一個大小爲 nn 的樹,它共有 nn 個結點與 n1n - 1 條邊,結點從 1n1 \sim n 編號。初始時每個結點上都有一個 1n1 \sim n 的數字,且每個 1n1 \sim n 的數字都只在恰好一個結點上出現。

接下來你需要進行恰好 n1n - 1 次刪邊操作,每次操作你需要選一條未被刪去的邊,此時這條邊所連接的兩個結點上的數字將會交換,然後這條邊將被刪去。

n1n - 1 次操作過後,所有的邊都將被刪去。此時,按數字從小到大的順序,將數字 1n1 \sim n 所在的結點編號依次排列,就得到一個結點編號的排列 PiP_i​。現在請你求出,在最優操作方案下能得到的字典序最小的 PiP_i​。

分析

我們不難有一個貪心的想法:枚舉每個數,將它們分別移動到儘可能小的節點上面去。

考慮把一個數字送到另一個數字上,對於一個點我們一共有三種限制:

  • 對於起始點,選中的邊必須先於其他所有邊刪掉;
  • 對於中轉點,選中的兩條邊必須順序刪掉;
  • 對於結束點,選中的邊必須在最後刪掉。

那麼這樣一來我們將刪的邊的順序排成一排,可以發現我們可以用雙向鏈表來維護。於是我們給每個節點 uu 都建立一個雙向鏈表。

但在計算答案的過程中,一定有一些是連不到一起的,這種情況我們直接把它們看成一堆鏈表就可以了。

於是就可以用兩次 DFS 解決這個問題:

枚舉每個數字,第一次 DFS 找出它能夠到達的最小的節點,第二次 DFS 更新鏈表。

總時間複雜度 O(N2)O(N^2)

各種情況的討論還是看代碼吧。。。太複雜了 我不想寫 寫不出來。。。

參考代碼

簡直是分類大討論啊。。。

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章