[学习笔记] 基环树
一、基环树
基环树其实 并不是 一棵树,而是一张 \(n\) 个点 \(n\) 条边的连通图。
一般的基环树就形如一棵 \(n\) 个节点的树再加上一条非树边。
扩展. 如果基环树不连通,就变成多个基环树组成的森林了。
Method 1. 将环拎出来,环上的每一个节点都挂了一棵子树,将子树信息合并,在环上处理即可。
Method 2. 将那一条非树边去掉,先按照树处理,再考虑那一条非树边。
N 代表非树边(
二、例题
CF711D
https://codeforces.com/problemset/problem/711/D
给定一张 \(n\) 个点 \(n\) 条边的 无向图,建图方式:给定 \(a_1, a_2, \dots, a_n\),对于任意的 \(1 \leq i \leq n\), 有 \(a_i \neq i\)。
\(V = \{1, 2, \dots, n\}, E = \{(1, a_1), (2, a_2), \dots, (n, a_n)\}, G = (V, E)\)。
求给每一条边定向的方案数,满足无环,对 \(10^9 + 7\) 取模。
\(1 \leq n \leq 2 \times 10^5\)。
题解
Lemma. 一个环(无向,点数至少为 \(2\)),定向之后是环的方案数为 \(2\),本质就是一个环可以顺时针走也可以逆时针走。
定向之后有环,肯定定向之前是一个环。
那么我们设环长为 \(c_1, c_2, \dots, c_k\)(其中 \(k\) 为环的数量),根据乘法原理,答案显然为:
第一个因式就是剩下的不在环上的边可以随便选,第二个就是避开所有无向环成为有向环的方案数。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
template < class T, int N >
struct DSU {
T fa[N];
inline void init (T n) {for (T i = 1;i <= n; ++ i) fa[i] = i;}
inline T find (T x) {return fa[x] == x ? x : fa[x] = find (fa[x]);}
inline void merge (T x, T y) {fa[find (x)] = find (y);}
inline bool query (T x, T y) {return find (x) == find (y);}
};
DSU < int, 200005 > tr;
int n, a[200005];
const ll mod = 1e9 + 7;
ll pw[200005];
vector < int > st[200005];
signed main () {
read (n);
tr.init (n);
pw[0] = 1;
for (int i = 1;i < 200005; ++ i) pw[i] = (pw[i - 1] + pw[i - 1]) % mod;
for (int i = 1;i <= n; ++ i) read (a[i]);
for (int i = 1;i <= n; ++ i) tr.merge (i, a[i]);
for (int i = 1;i <= n; ++ i) st[tr.find (i)].push_back (i);
tr.init (n);
int oth = n;
ll ans = 1;
for (int i = 1;i <= n; ++ i) {
if (!st[i].size ()) continue;
int S = -1, len = 0;
for (int u : st[i]) tr.fa[u] = u;
for (int u : st[i]) {
if (tr.query (u, a[u])) {
S = u;
break;
}
tr.merge (u, a[u]);
}
int cur = S;
while (true) {
len ++;
cur = a[cur];
if (cur == S) break;
}
oth -= len;
ans = ans * (pw[len] + mod - 2) % mod;
for (int u : st[i]) tr.fa[u] = u;
}
ans = ans * pw[oth] % mod;
ans = (ans % mod + mod) % mod;
printf ("%lld\n", ans);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
洛谷 P1453
https://www.luogu.com.cn/problem/P1453
定义一棵 \(n\) 个点 \(n\) 条边的基环树,每个点带权,求最大独立集。
\(1 \leq n \leq 10^5\)。
题解
考虑拎出来其中一个生成树。
设非树边为 \((u', v')\),那么我们以 \(u', v'\) 为根分别树形 dp 一下,那么答案就是 \(\max(f_{u', 0}, f_{v', 0})\)。
树形 dp:
设 \(f_{u, 0/1}\) 表示以 \(u\) 为根的子树,选不选(0/1) \(u\) 的最大权。
显然有:
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
template < class T, int N >
struct DSU {
T fa[N];
inline void init (T n) {for (T i = 1;i <= n; ++ i) fa[i] = i;}
inline T find (T x) {return fa[x] == x ? x : fa[x] = find (fa[x]);}
inline void merge (T x, T y) {fa[find (x)] = find (y);}
inline bool query (T x, T y) {return find (x) == find (y);}
};
DSU < int, 100005 > tr;
int n, w[100005], f[100005][2], u[100005], v[100005], exu, exv;
vector < int > G[100005];
inline void dfs (int node, int fa) {
f[node][0] = 0;
f[node][1] = w[node];
for (int nxt : G[node]) {
if (nxt != fa) {
dfs (nxt, node);
f[node][0] += max (f[nxt][0], f[nxt][1]);
f[node][1] += f[nxt][0];
}
}
}
signed main () {
read (n);
for (int i = 1;i <= n; ++ i) read (w[i]);
for (int i = 1;i <= n; ++ i) read (u[i]), read (v[i]), u[i] ++, v[i] ++;
tr.init (n);
for (int i = 1;i <= n; ++ i) {
if (!tr.query (u[i], v[i])) tr.merge (u[i], v[i]);
else exu = u[i], exv = v[i];
}
for (int i = 1;i <= n; ++ i) {
if (u[i] == exu && v[i] == exv) ;
else G[u[i]].push_back (v[i]), G[v[i]].push_back (u[i]);
}
int ans = -1;
for (int i = 1;i <= n; ++ i) f[i][0] = f[i][1] = 0;
dfs (exu, 0);
ans = max (ans, f[exu][0]);
for (int i = 1;i <= n; ++ i) f[i][0] = f[i][1] = 0;
dfs (exv, 0);
ans = max (ans, f[exv][0]);
double res = (double) (ans);
double k;
scanf ("%lf", &k);
res *= k;
printf ("%.1lf\n", res);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
洛谷 P2607
https://www.luogu.com.cn/problem/P2607
定义一棵 \(n\) 个点 \(n\) 条边的图,每个点带权,求最大独立集。
\(1 \leq n \leq 10^6\)。
题解
跟上题的思路一样,只需要分成多个基环树处理。
记得开 long long。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
template < class T, int N >
struct DSU {
T fa[N];
inline void init (T n) {for (T i = 1;i <= n; ++ i) fa[i] = i;}
inline T find (T x) {return fa[x] == x ? x : fa[x] = find (fa[x]);}
inline void merge (T x, T y) {fa[find (x)] = find (y);}
inline bool query (T x, T y) {return find (x) == find (y);}
};
DSU < int, 1000005 > tr;
int n, a[1000005], hate[1000005];
vector < int > st[1000005];
ll f[1000005][2];
int jzf, jzs;
int head[1000005], cnt;
struct Edge {
int to, nxt;
} ed[2000005];
inline void add_edge (int u, int v) {
cnt ++;
ed[cnt].to = v;
ed[cnt].nxt = head[u];
head[u] = cnt;
}
inline void dfs (int u, int fa) {
f[u][0] = 0;
f[u][1] = 1ll * a[u];
for (int i = head[u]; i ; i = ed[i].nxt) {
int v = ed[i].to;
if (v != fa) {
dfs (v, u);
if (max (f[v][0], f[v][1]) > 0) f[u][0] += max (f[v][0], f[v][1]);
if (f[v][0] > 0) f[u][1] += f[v][0];
}
}
}
signed main () {
read (n);
tr.init (n);
for (int i = 1;i <= n; ++ i) {
read (a[i]), read (hate[i]);
if (!tr.query (i, hate[i])) {
add_edge (i, hate[i]);
add_edge (hate[i], i);
}
tr.merge (i, hate[i]);
}
for (int i = 1;i <= n; ++ i) st[tr.find (i)].push_back (i);
ll ans = 0;
for (int i = 1;i <= n; ++ i) {
if (!st[i].size ()) continue;
for (int u : st[i]) tr.fa[u] = u;
int exu = -1, exv = -1;
ll U, V;
for (int u : st[i]) {
if (tr.query (u, hate[u])) {
exu = u, exv = hate[u];
break;
}
tr.merge (u, hate[u]);
}
jzf = exu, jzs = exv;
for (int u : st[i]) f[u][0] = f[u][1] = 0;
dfs (exu, 0);
U = f[exu][0];
for (int u : st[i]) f[u][0] = f[u][1] = 0;
dfs (exv, 0);
V = f[exv][0];
ans += max (U, V);
}
printf ("%lld\n", ans);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/