【模板】静态仙人掌

37 篇文章 0 订阅
27 篇文章 0 订阅

【模板】静态仙人掌 ⁡ \operatorname{【模板】静态仙人掌}

题目链接: luogu P5236 ⁡ \operatorname{luogu\ P5236} luogu P5236

题目背景

这是一道静态仙人掌 (Block Forest Data Structure) 的模板题。
如果您不知道什么是仙人掌,那么此处给出无向仙人掌图的定义:

任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。

题目

给你一个有 n n n 个点和 m m m 条边的仙人掌图,和 q q q 组询问
每次询问两个点 u , v u,v u,v ,求两点之间的最短路。

输入

第一行三个正整数 n , m , q n,m,q n,m,q ,意义如题目描述。
接下来 m m m 行,每行三个正整数 u , v , w u,v,w u,v,w ,表示 u , v u,v u,v 之间有一条权值为 w w w 的无向边。
然后 q q q 行,每行两个正整数 u , v u,v u,v ,询问 u u u v v v 的最短路。

输出

q q q 行,每行一个正整数,对应一次询问的结果。

样例输入1

9 10 2
1 2 1
1 4 1
3 4 1
2 3 1
3 7 1
7 8 2
7 9 2
1 5 3
1 6 4
5 6 1
1 9
5 7

样例输出1

5
6

样例解释

样例 1 1 1 中的仙人掌是这个样子的:
在这里插入图片描述
询问有两个,分别是询问 1 → 9 1\rightarrow 9 19 5 → 7 5\rightarrow 7 57 的最短路
显然答案分别为 5 5 5 6 6 6

样例输入2

9 10 3
1 2 1
2 3 1
2 4 4
3 4 2
4 5 1
5 6 1
6 7 2
7 8 2
8 9 4
5 9 2
1 9
5 8
3 4

样例输出2

7
5
2

数据范围

1 ≤ n , q ≤ 10000 1≤n,q≤10000 1n,q10000
1 ≤ m ≤ 20000 1\le m \le 20000 1m20000
1 ≤ w ≤ 1 0 5 1\le w \le 10^5 1w105

提示

请注意时限为 300 ms 300\text{ms} 300ms

思路

这道题是一个叫做圆方树的东西。

在这里插入图片描述

假设图示这样的,那我们把原来的图里面的点叫做圆点,如果图中有环,就建一个方点连向环的各个点,原来的这个环的边就抹掉。就像这样:
在这里插入图片描述
我们就把那些方点走到圆点的长度设为它到它父亲的最短的距离,找环用 tarjan 来求,圆点和圆点直接的边就不变。
然后对于求 x x x y y y 的最短路,我们先求出他们的 lca ,再分开讨论:

  1. lca 是圆点,就是普通的两点距离。
  2. lca 是方点,就找到 lca 的儿子中分别是 x x x y y y 的祖先的那两个点 X , Y X,Y X,Y ,那答案就是两个儿子分别到他们祖先的距离之和再加上他们祖先的最短距离(因为两祖先在一个环内,两条路最短的那条就是了),就是答案。

要开 l o n g   l o n g long\ long long long ,好像不用快读快输但是我用了。

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	ll x, to, nxt;
}e[400001], ee[400001];
ll n, m, q, x, y, z, w, ans;
ll nn, KK, KKK, lca, X, Y;
ll l[100001], fa[200001], dep[200001], dis[200001], diss[200001];
ll dfn[200001], low[200001], le[200001], lee[200010], fath[21][200001];

ll read() {//快读
	ll an = 0, zhengfu = 1;
	char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') zhengfu = -zhengfu;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		an = an * 10 + c - '0';
		c = getchar();
	}
	return an * zhengfu;
}

void add0(ll x, ll y, ll z) {//建原图
	e[++KK] = (node){z, y, le[x]}; le[x] = KK;
	e[++KK] = (node){z, x, le[y]}; le[y] = KK;
}

void add(ll x, ll y, ll z) {//建圆方树的图
	ee[++KKK] = (node){z, y, lee[x]}; lee[x] = KKK;
	ee[++KKK] = (node){z, x, lee[y]}; lee[y] = KKK;
}

void build(ll x, ll y, ll lon) {//建方点
	nn++;
	ll len = lon;
	for (ll i = y; i != fa[x]; i = fa[i]) {
		diss[i] = len;
		len += l[i];
	}
	diss[nn] = diss[x];
	diss[x] = 0;
	for (ll i = y; i != fa[x]; i = fa[i]) {
		len = min(diss[i], diss[nn] - diss[i]);
		add(i, nn, len);
	}
}

void dfs1(ll now) {//tarjan
	dfn[now] = low[now] = ++w;
	for (ll i = le[now]; i; i = e[i].nxt)
		if (e[i].to != fa[now]) {
			if (!dfn[e[i].to]) {
				fa[e[i].to] = now;
				l[e[i].to] = e[i].x;
				dfs1(e[i].to);
				low[now] = min(low[now], low[e[i].to]);
			}
			else low[now] = min(low[now], dfn[e[i].to]);
			if (low[e[i].to] > dfn[now]) add(now, e[i].to, e[i].x);//圆点和圆点连边
		}
	
	for (ll i = le[now]; i; i = e[i].nxt)
		if (fa[e[i].to] != now && dfn[now] < dfn[e[i].to])
			build(now, e[i].to, e[i].x);//找到环建方点
}

void dfs2(ll now) {//遍历来预处理
	dep[now] = dep[fath[0][now]] + 1;
	for (int i = 1; i <= 20; i++)
		fath[i][now] = fath[i - 1][fath[i - 1][now]];
	for (ll i = lee[now]; i; i = ee[i].nxt)
		if (ee[i].to != fath[0][now]) {
			fath[0][ee[i].to] = now;
			if (!dis[ee[i].to]) dis[ee[i].to] = dis[now] + ee[i].x;
				else dis[ee[i].to] = min(dis[ee[i].to], dis[now] + ee[i].x);
			dfs2(ee[i].to);
		}
}

ll LCA(ll x, ll y) {//求lca
	if (dep[x] < dep[y]) swap(x, y);
	for (ll i = 20; i >= 0; i--)
		if (dep[fath[i][x]] >= dep[y])
			x = fath[i][x];
	for (ll i = 20; i >= 0; i--)
		if (fath[i][x] != fath[i][y]) {
			x = fath[i][x];
			y = fath[i][y];
		}
	
	X = x;
	Y = y;
	if (x == y) return x;
	return fath[0][x];
}

void writec(ll x) {//快输
	if (x > 9) writec(x / 10);
	putchar(x % 10 + '0');
}

void write(ll x) {
	if (x < 0) writec(-x);
	writec(x);
	putchar('\n');
}

int main() {
	n = read();//读入
	m = read();
	q = read();
	nn = n;
	for (ll i = 1; i <= m; i++) {
		x = read();//读入
		y = read();
		z = read();
		add0(x, y, z);//建原来的图
	}
	
	dfs1(1);//建圆方树
	fath[0][1] = 1;
	dfs2(1);//初始化
	
	for (ll i = 1; i <= q; i++) {
		x = read();//读入
		y = read();
		lca = LCA(x, y);//求出lca
		if (lca > n) {//lca在方点
			ans = dis[x] - dis[X] + dis[y] - dis[Y];
			ll far = abs(diss[X] - diss[Y]);
			ans = ans + min(far, diss[lca] - far);
		}
		else ans = dis[x] - dis[lca] + dis[y] - dis[lca];//在圆点
		write(ans);//输出
	}
	
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值