[学习笔记] 基环树

一、基环树

基环树其实 并不是 一棵树,而是一张 \(n\) 个点 \(n\) 条边的连通图。

一般的基环树就形如一棵 \(n\) 个节点的树再加上一条非树边。

扩展. 如果基环树不连通,就变成多个基环树组成的森林了。

Method 1. 将环拎出来,环上的每一个节点都挂了一棵子树,将子树信息合并,在环上处理即可。

image

Method 2. 将那一条非树边去掉,先按照树处理,再考虑那一条非树边。

image

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\) 为环的数量),根据乘法原理,答案显然为:

\[2^{n - \sum\limits_{i = 1}^{k} c_i} \times \prod_{i = 1}^{k} (2^{c_i} - 2) \]

第一个因式就是剩下的不在环上的边可以随便选,第二个就是避开所有无向环成为有向环的方案数。

#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\) 的最大权。

显然有:

\[f_{u, 0} = \sum_{v \in son_u} \max(f_{v, 0}, f_{v, 1}) \]

\[f_{u, 1} = \sum_{v \in son_u} f_{v, 0} \]

#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 ?
*/
posted @ 2023-06-12 17:02  CountingGroup  阅读(118)  评论(0编辑  收藏  举报