【模板】静态仙人掌 \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
1→9 和
5
→
7
5\rightarrow 7
5→7 的最短路
显然答案分别为
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
1≤n,q≤10000
1
≤
m
≤
20000
1\le m \le 20000
1≤m≤20000
1
≤
w
≤
1
0
5
1\le w \le 10^5
1≤w≤105
提示
请注意时限为 300 ms 300\text{ms} 300ms
思路
这道题是一个叫做圆方树的东西。
假设图示这样的,那我们把原来的图里面的点叫做圆点,如果图中有环,就建一个方点连向环的各个点,原来的这个环的边就抹掉。就像这样:
我们就把那些方点走到圆点的长度设为它到它父亲的最短的距离,找环用 tarjan 来求,圆点和圆点直接的边就不变。
然后对于求
x
x
x 到
y
y
y 的最短路,我们先求出他们的 lca ,再分开讨论:
- lca 是圆点,就是普通的两点距离。
- 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;
}