圓方樹學習筆記
阿新 • • 發佈:2020-08-16
寫這個東西只是記錄一下我學過圓方樹 \(\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; }
性質
- 圓方數上原點和方點交替出現。
- 圓方數的點數小於 \(2n\),因此做題時注意開兩倍空間。
- 圓方樹上所有不是葉子節點的圓點都是原圖中的一個割點。
應用
道路相遇
題意:
\(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\)
程式碼:
#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;
}