圆方树

定义

割点:无向图中,若删除点及其连边,连通块变多,那么被删除的点为割点

点双连通:若无向图中点对 \(x,y\),删除任意非 \(x\) 和非 \(y\) 节点后,\(x\)\(y\) 任然连通,陈 \(x,y\) 点双连通

点双连通子图:无向图中的一个子图 \(G\)\(G\) 中任意 2 点都是联通的,那么称 \(G\) 为原图的点双连通子图子图

点双连通分量:无向图中的极大点双连通子图,称为 V-DCC

点双连通不具有传递性,边双连通具有传递性

性质

  1. 无向图至少有 3 个点,才可能有割点
  2. dfs 搜索树中的根结点有 2 个及以上的子节点时,才可能是割点
  3. 1 个割点可能在多个点双中

圆方树

定义:将无向图转化为树形结构,解决“必经点”问题的数据结构,通过构建一棵树,使得书上两点路径上的点都是原图的必经点

圆点:原无向图 \(G\) 中的点,仍然保留在圆方数中,称之为圆点

方点:将每个点双内的圆点连边

树边:每个方点向对应点双内的圆点连边

性质

  1. 圆方数中的总点数 \(= 原图点数 + 点双个数\),上限为 \(2 \times n + 1\)
  2. 圆点是被方点隔开的,一条边的两个端点一定是圆点和方点
  3. 圆点的度数就是包含该点的点双个数。
  4. 圆方树删除点 \(x\) 后剩余节点的连通性与原图中删除 \(x\) 后的连通性等价。
  5. 原图中 \(x\)\(y\) 的路径的必经点就是圆方数上 \(x\)\(y\) 经过的所有的圆点
  6. 圆点为割点时才可能有超过一个儿子结点

code

代码形如 tarjan

void DFS(int x, int fa = -1) {
  dfn[st[++tot] = x] = low[x] = ++sum;
  for (int i : t[x]) {
    if (i == fa) continue;
    if (!dfn[i]) {
      DFS(i, x);
      low[x] = min(low[x], low[i]);
      if (low[i] >= dfn[x]) {
        ans1++;
        for (g[++id].push_back((g[x].push_back(id), x)), indeg[id]++, indeg[x]++, st[tot + 1] = 0, sz[id] = 1; st[tot + 1] != i; tot--) {
          g[id].push_back(st[tot]), g[st[tot]].push_back(id), indeg[st[tot]]++, indeg[id]++, sz[id]++;
        }
      }
    } else {
      low[x] = min(low[x], dfn[i]);
    }
  }
}

仙人掌

用圆方树来处理仙人掌

P5236

对于一组询问 \((x, y)\),转化为求圆方树上 \(x\)\(y\) 的路径,定义两点距离(\(dis\))为两点再环上的较短路,到根的距离我们也用 \(dis\),不过不写根。

将圆点到方点的边权,设为圆点到方点的父亲的距离,而方点到父亲没有边权

\(lca(x,y)\) 为圆点,有 \(dis_x+dis_y-2\times dis_{lca}\)

\(lca(x,y)\) 为方点,将式子改为:\(dis_x-dis_{x'}+dis_y-dis_{y'}+dis(x,y)\)

\(dis\) 我们选择用再换上跑前缀和的方式来处理

code

#include <iostream>
#include <cmath>
#include <map>

using namespace std;
using pil = pair<int, int>;

const int MaxN = 120000, MaxK = 23;

struct S {
  int to, w, nxt;
} e[MaxN << 1], g[MaxN << 1];

int f[MaxN][MaxK], hd[MaxN], sz[MaxN], dh[MaxN], dfn[MaxN], low[MaxN], res[MaxN], grtd[MaxN], d[MaxN], cnt = 1, cg = 1, sum, tot, n, m, id, q;
pil st[MaxN];
map<int, int> dis[MaxN], www[MaxN];

void add(int u, int v, int w) {
	g[++cg] = {v, w, dh[u]}, dh[u] = cg;
	g[++cg] = {u, w, dh[v]}, dh[v] = cg;
}

void build(int x, int fe = 0) {
	dfn[x] = low[x] = ++sum;
	st[++tot] = {x, e[fe].w};
	for (int j = hd[x]; ~j; j = e[j].nxt) {
		int i = e[j].to;
		if (j == (fe ^ 1)) continue;
		if (!dfn[i]) {
			f[i][0] = x;
			build(i, j);
			low[x] = min(low[x], low[i]);
			if (low[i] >= dfn[x]) {
				id++;
				res[id] = www[x][st[tot].first];
				for (int k = tot; st[k].first != x; k--) {
					dis[st[k].first][id] = res[id], res[id] += st[k].second;
				}
				dis[x][id] = res[id];
				for (add(id, x, min(res[id] - dis[x][id], dis[x][id])), st[tot + 1] = {0, 0}, sz[id] = 1; st[tot + 1].first != i; tot--) {
					add(id, st[tot].first, min(res[id] - dis[st[tot].first][id], dis[st[tot].first][id])), sz[id]++;
				}
			}
		} else if (dfn[i] < dfn[x]) {
			low[x] = min(low[x], dfn[i]);
		}
	}
}

int W(int x, int y, int id) {
	int tmp = abs(dis[x][id] - dis[y][id]);
	if (sz[id] == 2) return tmp;
	return min(tmp, res[id] - tmp);
}

void DFS(int x, int fe = 0) {
	f[x][0] = g[fe ^ 1].to;
	for (int i = 1; i < MaxK; i++) {
		f[x][i] = f[f[x][i - 1]][i - 1];
	}
	for (int j = dh[x]; ~j; j = g[j].nxt) {
		int i = g[j].to;
		if (j == (fe ^ 1)) continue;
		grtd[i] = grtd[x] + g[j].w, d[i] = d[x] + 1;
		DFS(i, j);
	}
}

int query(int x, int y) {	
	for (int i = MaxK - 1; ~i; i--) {
		if (f[x][i] && d[f[x][i]] >= d[y]) x = f[x][i];
	}
	return x;
}

pil lca(int x, int y) {
	if (d[x] < d[y]) swap(x, y);
	x = query(x, y);
	if (x == y) return {x, y};
	for (int i = MaxK - 1; ~i; i--) {
		if (f[x][i] && f[y][i] && f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return {x, y};
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m >> q, id = n;
	fill(hd, hd + MaxN - 1, -1);
	fill(dh, dh + MaxN - 1, -1);
	for (int i = 1, u, v, w; i <= m; i++) {
		cin >> u >> v >> w;
    if (u == v) continue;
    if (www[u].count(v)) {
      www[u][v] = www[v][u] = min(www[u][v], w);
      continue;
    }
    www[u][v] = www[v][u] = w;
    e[++cnt] = {v, w, hd[u]}, hd[u] = cnt;
		e[++cnt] = {u, w, hd[v]}, hd[v] = cnt;
	}
	build(1), DFS(1);
	for (int u, v, x, y; q; q--) {
		cin >> u >> v;
		pil p = lca(u, v);
		x = p.first, y = p.second;
		if (x == y) {
			cout << grtd[u] + grtd[v] - 2 * grtd[x] << '\n';
		} else if (x > n && y > n) {
			cout << grtd[u] + grtd[v] - 2 * grtd[f[x][0]] << '\n';
		} else {
			cout << grtd[u] + grtd[v] - grtd[y] - grtd[x] + W(x, y, f[x][0]) << '\n';
		}
	}
	return 0;
}
posted @ 2024-08-21 21:33  yabnto  阅读(13)  评论(0编辑  收藏  举报
  1. 1 イエスタデイ(翻自 Official髭男dism) 茶泡饭,春茶,kobasolo
  2. 2 光辉岁月 Audio artist
  3. 3 名前を呼ぶよ Audio artist
  4. 4 战歌 Audio artist
  5. 5 時を越えた想い Audio artist
  6. 6 所念皆星河 Audio artist
  7. 7 See you again Audio artist
イエスタデイ(翻自 Official髭男dism) - 茶泡饭,春茶,kobasolo
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 藤原聡

作曲 : 藤原聡

何度失ったって

取り返して見せるよ

雨上がり 虹がかかった空みたいな

君の笑みを

例えばその代償に

誰かの表情を

曇らせてしまったっていい

悪者は僕だけでいい

本当はいつでも

誰もと思いやりあっていたい

でもそんな悠長な理想論は

ここで捨てなくちゃな

遥か先で 君へ 狙いを定めた恐怖を

遥か先で 君へ 狙いを定めた恐怖を

どれだけ僕は

はらい切れるんだろう?

半信半疑で 世間体

半信半疑で 世間体

気にしてばっかのイエスタデイ

ポケットの中で怯えたこの手は

まだ忘れられないまま

「何度傷ついたって

「何度傷ついたって

仕方ないよ」と言って

うつむいて君が溢した

儚くなまぬるい涙

ただの一粒だって

僕を不甲斐なさで 溺れさせて

理性を奪うには十分過ぎた

街のクラクションもサイレンも

街のクラクションもサイレンも

届きやしないほど

遥か先へ進め 身勝手すぎる恋だと

遥か先へ進め 身勝手すぎる恋だと

世界が後ろから指差しても

振り向かず進め必死で

振り向かず進め必死で

君の元へ急ぐよ

道の途中で聞こえたSOS さえ

気づかないふりで

バイバイイエスタデイ ごめんね

バイバイイエスタデイ ごめんね

名残惜しいけど行くよ

いつかの憧れと違う僕でも

ただ1人だけ 君だけ

守るための強さを

何よりも望んでいた この手に今

遥か先へ進め

遥か先へ進め

幼すぎる恋だと

世界が後ろから指差しても

迷わずに進め 進め

2人だけの宇宙へと

ポケットの中で震えたこの手で今

君を連れ出して

未来の僕は知らない

だから視線は止まらない

謎めいた表現技法

意味深な君の気性

アイラブユーさえ

アイラブユーさえ

風に 飛ばされそうな時でも

不器用ながら繋いだ この手はもう

決して離さずに

虹の先へ