基環樹

Chen_jr發表於2024-07-21

1 基環樹概念

1.1 定義

首先,基環樹並不是一顆嚴格的樹。它是一張由 \(n\) 個節點,\(n\) 條邊組成的圖。

1.2 無向聯通圖上的基環樹

首先,一棵樹有 \(n\) 個節點,\(n-1\) 條邊。那麼基環樹就可以看做是在一棵樹上加了一條邊,這樣多出了一個環(因此基環樹也被稱作環套樹)。

如下圖所示:

934609502a934666bdfc4a11abfae0e9 (557×557) (byteimg.com)

1.3 有向聯通圖上的基環樹

有向圖上根據邊的方向再次分為兩類

1.3.1 內向樹

每個節點的出度為 \(1\),如下圖:

9d4000e0e95941eea34408758e66ebc9 (557×557) (byteimg.com)

1.3.2 外向樹

每個節點的入度為 \(1\),如下圖:

e52f6a1cfbdda98626d97e951219a15d.png (557×557) (bdstatic.com)

1.4 非聯通圖上的基環樹

如果整個圖非聯通,那麼又會形成基環樹森林、基環外向樹森林、基環內向樹森林。

2 基環樹處理方式

上面的定義其實對於做題沒有什麼幫助,同時基環樹的題目通常也要結合樹形或環形 dp 求解。下面介紹一些基本處理方式。

2.1 找環

最基本的一步:先找出基環樹中環在哪裡。

以無向連通圖為例,程式碼很好想也很好實現:

void dfs(int u, int fa) {
	if(ins[u]) {//在棧內,說明又走回來了
		int v;
		mark[u] = 1;
		while(s[top] != u) {//這些節點均為環上的點
			mark[s[top]] = 1;
			top--;
		}
		return ;
	}
	if(vis[u] == 1) return ;//已經訪問(訪問並不等價於在棧中)
	s[++top] = u;
	vis[u] = ins[u] = 1;
	for(int i = head[u]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(to == fa) continue;
		dfs(to, x);
	}
	top--;
	ins[u] = false;//彈出棧
	return;
}

2.2 樹形 dp 處理

由於基環樹與普通的樹十分接近,因此可以考慮去掉環上的一條邊,使整張圖變為一顆樹之後再開始 dp。

又因為我們刪去了一條邊,所以會有兩個點的關係被刪除,所以這種方法一般要 dp 兩次。

2.3 環形 dp 處理

首先我們將環拎出來,這樣就可以看成是一些樹掛在這個環上。我們將所有子樹的資訊算出來之後合併到環上的節點,這樣就可以環形 dp 求解了。

3 例題

對於基環樹,概念是沒有什麼用的,所以直接看一道例題。

3.1 [ZJOI2018] 騎士

首先這道題和 沒有上司的舞會 很像,只是變成了基環樹。

我們考慮樹形 dp 處理,先求出環上的一條邊,以這條邊上的兩點作樹形 dp,分別得到 \(f(x,0)\)\(g(y,0)\)

由於兩者只能取一,所以在兩個答案中取 \(\max\) 即可。

剩下的注意細節。

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int Maxn = 2e6 + 5;

int n, w[Maxn];

int head[Maxn], edgenum = 1;
struct node {
	int nxt, to;
}edge[Maxn];

void add(int from, int to) {
	edge[++edgenum] = {head[from], to};
	head[from] = edgenum;
}

int st, ed, num;
int vis[Maxn], ins[Maxn];

void fcir(int x, int fa) {
	if(vis[x]) return ;
	vis[x] = ins[x] = 1;
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(to == fa) continue;
		if(ins[to]) {
			st = x, ed = to, num = i;
			continue;
		}
		fcir(to, x);
	}
	ins[x] = 0;
}

int dp[Maxn][2];

void dfs(int x, int fa) {
	dp[x][0] = 0;
	dp[x][1] = w[x];
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(to == fa) continue;
		if(i == num || (i ^ 1) == num)  {
			continue;
		}
		dfs(to, x);
		dp[x][0] += max(dp[to][0], dp[to][1]);
		dp[x][1] += dp[to][0];
	}
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		int v;
		cin >> w[i] >> v;
		add(i, v), add(v, i);
	}
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		if(vis[i]) continue;
		fcir(i, 0);
		dfs(st, 0);
		int ans1 = dp[st][0];
		memset(dp, 0, sizeof dp);
		dfs(ed, 0);
		ans += max(ans1, dp[ed][0]);
	}
	cout << ans ;
	return 0;
}

相關文章