1 基環樹概念
1.1 定義
首先,基環樹並不是一顆嚴格的樹。它是一張由 \(n\) 個節點,\(n\) 條邊組成的圖。
1.2 無向聯通圖上的基環樹
首先,一棵樹有 \(n\) 個節點,\(n-1\) 條邊。那麼基環樹就可以看做是在一棵樹上加了一條邊,這樣多出了一個環(因此基環樹也被稱作環套樹)。
如下圖所示:
1.3 有向聯通圖上的基環樹
有向圖上根據邊的方向再次分為兩類
1.3.1 內向樹
每個節點的出度為 \(1\),如下圖:
1.3.2 外向樹
每個節點的入度為 \(1\),如下圖:
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;
}