1. 程式人生 > 實用技巧 >圓方樹學習筆記

圓方樹學習筆記

寫這個東西只是記錄一下我學過圓方樹 \(\text{/cy}\)

建樹

圓方樹是一種將圖變成樹的方法。

首先,把原圖中的所有點都看成圓點,我們需要求出圖中所有的點雙連通分量,可以使用 Tarjan 演算法。

然後,在每一個點雙連通分量中間建立一個方點,將此點雙連通分量中的所有點向這個方點連邊。

這樣就可以把圖轉成樹,利用一些樹上的性質解題了。

放一張來自 WC PPT 的圖。

程式碼:

int dfn[N], low[N], tim, stk[N], tp, cnt;
int tot, head[N], headc[N], ver[M], nxt[M];
//headc: 原圖   head: 圓方樹

inline void add(int h[], int u, int v)
{
	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
}

void Tarjan(int u)
{
	dfn[u] = low[u] = ++tim, stk[++tp] = u;
	for (int i = headc[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
			if (low[v] == dfn[u])
			{
				++cnt;
				int y = -1;
				do
				{
					y = stk[tp--];
					add(head, y, cnt), add(head, cnt, y);
				} while (y != v);
				add(head, cnt, u), add(head, u, cnt);
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

//主函式
int main()
{
    n = gi <int> (), m = gi <int> ();
    for (int i = 1; i <= m; i+=1)
    {
        int u = gi <int> (), v = gi <int> ();
        add(headc, u, v), add(headc, v, u);
    }
    cnt = n;
    for (int i = 1; i <= n; i+=1)
        if (!dfn[i]) Tarjan(i), --tp;
}

性質

  1. 圓方數上原點和方點交替出現。
  2. 圓方數的點數小於 \(2n\),因此做題時注意開兩倍空間。
  3. 圓方樹上所有不是葉子節點的圓點都是原圖中的一個割點。

應用

道路相遇

題面

題意:

\(q\) 次詢問,每次詢問點 \(u\) 到點 \(v\) 所有簡單路徑的交。

建出圓方樹,問題就轉換成圓方樹上點 \(u\) 與點 \(v\) 之間圓點的個數。

記錄一下樹上字首和,樹上差分即可。

程式碼:

#include <bits/stdc++.h>
#define DEBUG fprintf(stderr, "Passing [%s] line %d\n", __FUNCTION__, __LINE__)
#define File(x) freopen(x".in","r",stdin); freopen(x".out","w",stdout)

using namespace std;

typedef long long LL;
typedef pair <int, int> PII;
typedef pair <int, PII> PIII;

template <typename T>
inline T gi()
{
	T f = 1, x = 0; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int INF = 0x3f3f3f3f, N = 1000003, M = N << 2;

int n, m, q;
int tot, head[N], headc[N], ver[M], nxt[M];
int dfn[N], low[N], tim, stk[N], tp, cnt;
int sum[N];

inline void add(int h[], int u, int v)
{
	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
}

void Tarjan(int u)
{
	dfn[u] = low[u] = ++tim, stk[++tp] = u;
	for (int i = headc[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
			if (low[v] == dfn[u])
			{
				++cnt;
				int y = -1;
				do
				{
					y = stk[tp--];
					add(head, y, cnt), add(head, cnt, y);
				} while (y != v);
				add(head, cnt, u), add(head, u, cnt);
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

void dfs(int u, int f)
{
	sum[u] = (u <= n) + sum[f];
	for (int i = head[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (v == f) continue;
		dfs(v, u);
	}
}

int dep[N], fa[N], sz[N], son[N], topp[N];

void dfs1(int u, int f)
{
	dep[u] = dep[f] + 1, fa[u] = f, sz[u] = 1;
	for (int i = head[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (v == f) continue;
		dfs1(v, u);
		sz[u] += sz[v];
		if (sz[v] > sz[son[u]]) son[u] = v;
	}
}

void dfs2(int u, int f)
{
	topp[u] = f;
	if (!son[u]) return;
	dfs2(son[u], f);
	for (int i = head[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}

inline int LCA(int u, int v)
{
	while (topp[u] != topp[v])
	{
		if (dep[topp[u]] < dep[topp[v]]) swap(u, v);
		u = fa[topp[u]];
	}
	if (dep[u] < dep[v]) return u;
	return v;
}

int main()
{
	//File("");
	n = gi <int> (), m = gi <int> ();
	for (int i = 1; i <= m; i+=1)
	{
		int u = gi <int> (), v = gi <int> ();
		add(headc, u, v), add(headc, v, u);
	}
	cnt = n;
	Tarjan(1);
	dfs(1, 0);
	q = gi <int> ();
	dfs1(1, 0); dfs2(1, 1);
	while (q--)
	{
		int u = gi <int> (), v = gi <int> ();
		int lca = LCA(u, v);
		printf("%d\n", sum[u] + sum[v] - sum[lca] - sum[fa[lca]]);
	}
	return 0;
}

APIO2018 鐵人兩項

題面

題意:

問有多少組 \(s\)\(c\)\(f\),滿足存在從 \(s\)\(c\) 和從 \(c\)\(f\) 的簡單路徑。

首先介紹一個點雙的性質:對於一個點雙中的兩個點 \(u\)\(v\),它們之間簡單路徑的並集恰好等於這個點雙。

然後問題就轉換成了:固定 \(s\)\(f\),問有多少個合法的 \(c\)

考慮圓方樹上兩圓點在原圖中所有簡單路徑的並,將這個問題轉換到圓方樹上就變成 兩圓點之間的路徑路徑上方點所在點雙中的所有點

這個問題很好求解,我們把圓方樹上每個方點的權值設為這個點雙中的點數,圓點的權值設為 \(-1\)

,答案即為圓方樹上 \(\sum\) 兩點之間所有點的權值之和。

程式碼:

#include <bits/stdc++.h>
#define DEBUG fprintf(stderr, "Passing [%s] line %d\n", __FUNCTION__, __LINE__)
#define File(x) freopen(x".in","r",stdin); freopen(x".out","w",stdout)

using namespace std;

typedef long long LL;
typedef pair <int, int> PII;
typedef pair <int, PII> PIII;
typedef pair <LL, int> PLI;

template <typename T>
inline T gi()
{
	T f = 1, x = 0; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int INF = 0x3f3f3f3f, N = 200003, M = N << 3;

int n, m;
int tot, head[N], headc[N], ver[M], nxt[M];
int dfn[N], low[N], tim, stk[N], tp;
int val[N], sz[N];
int cnt;
LL ans;
int num;

inline void add(int h[], int u, int v)
{
	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
}

void Tarjan(int u)
{
	++num;
	dfn[u] = low[u] = ++tim, stk[++tp] = u;
	for (int i = head[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
			if (low[v] == dfn[u])
			{
				val[++cnt] = 0;
				int y = -1;
				do
				{
					y = stk[tp--];
					add(headc, y, cnt), add(headc, cnt, y);
					++val[cnt];
				} while (y != v);
				add(headc, u, cnt), add(headc, cnt, u);
				++val[cnt];
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

void dfs(int u, int f, int mn)
{
	sz[u] = (u <= n);
	for (int i = headc[u]; i; i = nxt[i])
	{
		int v = ver[i];
		if (v == f) continue;
		dfs(v, u, mn);
		ans += (LL)2 * val[u] * sz[u] * sz[v];
		sz[u] += sz[v];
	}
	ans += (LL)2 * val[u] * sz[u] * (mn - sz[u]);
}

int main()
{
	//File("");
	n = gi <int> (), m = gi <int> ();
	for (int i = 1; i <= m; i+=1)
	{
		int u = gi <int> (), v = gi <int> ();
		add(head, u, v), add(head, v, u);
	}
	for (int i = 1; i <= n * 2; i+=1) val[i] = -1;
	cnt = n;
	for (int i = 1; i <= n; i+=1)
		if (!dfn[i])
		{
			num = 0;
			Tarjan(i);
			--tp;
			dfs(i, 0, num);
		}
	printf("%lld\n", ans);
	return 0;
}

參考學習

  1. https://www.cnblogs.com/PinkRabbit/p/10446473.html
  2. https://www.cnblogs.com/cjyyb/p/9098400.html