圆方树总结

  • 圆方树:一种将由图转化而成的树,从而大大了增加题目的可解性,且大多广泛用于仙人掌图中。
  • 针对仙人掌图上的圆方树:仙人掌是指一条边至多只被一个环包含的无向图。
  • 树上的点:圆方树上分为两类点,一类是圆点,一类是方点。圆点即原图中所有的点,方点即为了去环而新添加进去的,满足一定性质的点。
  • 构造思路:圆圆边直接加入,对于仙人掌中的任意一个环,每个环上的点在圆方树上对应的圆点向这个环对应的方点连边,方点为一个新建节点。
  • 环的根:指定一个圆点为圆方树的根,把方点的父亲叫做这个方点对应的环的根。
  • 圆方边边权:若一个在环上的圆点不是环的根,它到对应方点的边权为到环的根的最短距离,环的根到环所对应的方点的边权为零。

解题:

  • 多数是为了可以用树上的算法,例如倍增、树剖解决问题,以两点间路径的问题为例:
  • 若 \(lca\) 是圆点,那么答案就是路径上的贡献;
  • 若 \(lca\) 是方点,则找到进入这个环的两个点,这两个点之间的有两条路径,选择合题意的一条加入贡献。
  • 在树链剖分中,进入一个环的两个点有两种情况:一是一个为 \(dfs\) 序比 \(lca\) 大 \(1\) 的点,即 \(lca\) 所在重链上的儿子,另一个为最后经过的 \(top\);二是最后经过的两个 \(top\)。

洛谷模板题:

#include <cmath>
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 40000 + 10;
int n, m, q, head_g[maxn], head_t[maxn], st[17][maxn], edge_num_g, edge_num_t, dfn_num, col_num, top;
int dfn[maxn], low[maxn], sta[maxn];
long long deep[maxn], dis[maxn], sum[maxn], sccsum[maxn];

struct Edge { int v, nxt; long long w; } edge_g[maxn << 2], edge_t[maxn << 2];

inline void Add_edge_g(int u, int v, long long w) {
  edge_g[++edge_num_g].v = v, edge_g[edge_num_g].w = w, edge_g[edge_num_g].nxt = head_g[u], head_g[u] = edge_num_g;
}

inline void Add_edge_t(int u, int v, long long w) {
  edge_t[++edge_num_t].v = v, edge_t[edge_num_t].w = w, edge_t[edge_num_t].nxt = head_t[u], head_t[u] = edge_num_t;
}

inline void Tarjan(int x, int p) {
  dfn[x] = low[x] = ++dfn_num, sta[++top] = x;
  for(int i = head_g[x]; i; i = edge_g[i].nxt) if( edge_g[i].v != p ) {
    if( dfn[edge_g[i].v] == 0 ) {
      sum[edge_g[i].v] = sum[x] + edge_g[i].w, Tarjan(edge_g[i].v, x), low[x] = min(low[x], low[edge_g[i].v]);
      if( low[edge_g[i].v] > dfn[x] ) Add_edge_t(x, edge_g[i].v, edge_g[i].w), Add_edge_t(edge_g[i].v, x, edge_g[i].w);  // 树边,圆圆边加入
    }
    else if( dfn[edge_g[i].v] < low[x] ) {  // 返祖边,得环,建立方点及圆方边
      sccsum[++col_num] = sum[x] - sum[edge_g[i].v] + edge_g[i].w;  // 得环长,col_num 认为是方点编号
      for(int j = top; sta[j] != edge_g[i].v; --j) {
        int _w = min(sum[sta[j]] - sum[edge_g[i].v], sccsum[col_num] - sum[sta[j]] + sum[edge_g[i].v]); // 到此环的根的距离,即圆方边边权
        Add_edge_t(n + col_num, sta[j], _w), Add_edge_t(sta[j], n + col_num, _w);
      }
      Add_edge_t(n + col_num, edge_g[i].v, 0), Add_edge_t(edge_g[i].v, n + col_num, 0); // 环的根所对应的圆方边权为 0
      low[x] = dfn[edge_g[i].v];
    }
  }
  --top;
}

inline void Deep_fs(int x, int p) {
  for(int i = 1; i < 17; ++i) st[i][x] = st[i - 1][st[i - 1][x]];
  for(int i = head_t[x]; i; i = edge_t[i].nxt) if( edge_t[i].v != p ) {
    st[0][edge_t[i].v] = x;
    dis[edge_t[i].v] = dis[x] + edge_t[i].w, deep[edge_t[i].v] = deep[x] + 1, Deep_fs(edge_t[i].v, x);
  }
}

inline long long Querry(int x, int y) {
  int lca = 0, res = dis[x] + dis[y];
  if( deep[x] < deep[y] ) swap(x, y);
  for(int i = 16; i >= 0; --i) if( deep[st[i][x]] >= deep[y] ) x = st[i][x];
  if( x == y ) lca = x;
  else {
    for(int i = 16; i >= 0; --i) if( st[i][x] != st[i][y] ) x = st[i][x], y = st[i][y];
    lca = st[0][x];
  }
  if( lca <= n ) return res - (dis[lca] << 1);
  if( dfn[x] > dfn[y] ) swap(x, y);
  return res - dis[x] - dis[y] + min(sum[y] - sum[x], sccsum[lca - n] - sum[y] + sum[x]);
}

int main(int argc, char const *argv[])
{
  scanf("%d%d%d", &n, &m, &q);
  for(int u, v, w, i = 1; i <= m; ++i) scanf("%d%d%d", &u, &v, &w), Add_edge_g(u, v, w), Add_edge_g(v, u, w);
  deep[1] = 1, Tarjan(1, 0), Deep_fs(1, 0);
  for(int u, v, i = 1; i <= q; ++i) scanf("%d%d", &u, &v), printf("%lld\n", Querry(u, v));

  return 0;
}

原文地址:https://www.cnblogs.com/nanjoqin/p/11293720.html

时间: 2024-11-08 12:48:57

圆方树总结的相关文章

圆方树学习

圆方树是一种数据结构. 这个东西原始的出处应该是paper <Maintaining bridge-connected and biconnected components on-line> tarjan和另外一个人写的...当时叫forest data structure 然后这个东西似乎已经流行很久了?http://blog.csdn.net/PoPoQQQ/article/details/49513819 cjk大爷最近发了一篇博客写这个:http://immortalco.blog.u

【BZOJ】2125: 最短路 圆方树(静态仙人掌)

[题意]给定带边权仙人掌图,Q次询问两点间最短距离.n,m,Q<=10000 [算法]圆方树处理仙人掌问题 [题解]树上的两点间最短路问题,常用倍增求LCA解决,考虑扩展到仙人掌图. 先对仙人掌图建圆方树,圆圆边和原图边权一致.对于每个方点代表的环,记深度最小的点为x,则圆方边的边权是圆点到x的最短距离. 若lca(u,v)为圆点,则两点间最短路转化为圆方树上dis[u]+dis[v]-2*dis[lca].(向上延伸的路径,经过环则必然经过每个方点的x,计算无误) 若lca(u,v)为方点,则

CF487E Tourists 【圆方树 + 树剖 + 堆】

题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用了一对一的特性优化了复杂度 如此询问时如果\(lca\)为方点,再询问一下\(lca\)的父亲即可 复杂度\(O(qlog^2n)\) #include<algorithm> #include<iostream> #include<cstring> #include<

【P4320】 道路相遇 (圆方树+LCA)

题目链接 题意:给一张无向图和\(M\)个询问,问\(u,v\)之间的路径的必经之点的个数. 对图建出圆方树,然后必经之点就是两点路径经过的原点个数,用\((dep[u]+dep[v]-dep[LCA]*2)/2+1\)即可算出. 什么你不知道圆方树(说的跟我知道一样) \(APIO2018\)出来的黑科技,详见\(APIO2018\)铁人两项. 就是对每个点双新建一个点,然后让点双里所有点都对这个点连边. 看图. #include <cstdio> const int MAXN = 5000

Tourists——圆方树

CF487E Tourists 一般图,带修求所有简单路径代价. 简单路径,不能经过同一个点两次,那么每个V-DCC出去就不能再回来了. 所以可以圆方树,然后方点维护一下V-DCC内的最小值. 那么,从任意一个割点进入这个DCC,必然可以绕一圈再从另一个割点出去. 所以,路径上的最小值,就是圆方树路径上的最小值.方点的最小值就是在这个DCC中走一走得到的. 树链剖分+线段树维护路径 用堆维护方点四周的圆点的最小值.然后更新. 一个问题是: 更新一个割点圆点,会影响到四周所有的方点.暴力更新,菊花

CF487E Tourists 圆方树、树链剖分

传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就是一个反例 上面这个图如果缩点双会缩成\(3\)个,但是缩边双会将整个图缩成\(1\)个点. 假如我们询问的是\((1,4)\)之间的简单路径,而图中权值最小的点为\(7\)号点,那么如果缩成了边双联通分量,你的答案会是\(7\)号点的权值,意即认为可以走到\(7\)号点,但实际上如果到\(7\)号

道路相遇 圆方树

道路相遇 基础圆方树. 不会圆方树看我另一篇文章 LG传送门 发现必经之路上的点一定是简单路径上的点,可以自己手玩.处理无向图上的简单路径,考虑把圆方树建出来,发现答案就是园方树上两点间圆点个数.由于广义园方树上圆方点相间,可以用深度表示答案,发现答案就是\((dep[u] + dep[v] - 2 * dep[lca]) / 2 + 1\). #include <cstdio> #include <cctype> #include <vector> #define R

[SDOI2018]战略游戏 圆方树,树链剖分

[SDOI2018]战略游戏 这题是道路相遇(题解)的升级版,询问的两个点变成了\(S\)个点. LG传送门 还是先建出圆方树,考虑对于询问的\(S\)个点,答案就是圆方树上能包含这些点的最小连通块中的圆点个数减去\(S\).问题变成了怎样求这样的连通块中的圆点个数,直接给结论吧:先搞出树的dfs序,把询问的点按dfs序从小到大排一遍序,每次把答案加上第\(i\)和第\(i + 1\)个点之间的圆点个数,但是不算lca,再加上第\(1\)个和第\(S\)个点之间的圆点个数,然后除以二就得到了这个

[圆方树] Luogu P4630 Duathlon 铁人两项

题目描述 比特镇的路网由 mm 条双向道路连接的 nn 个交叉路口组成. 最近,比特镇获得了一场铁人两项锦标赛的主办权.这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程. 比赛的路线要按照如下方法规划: 先选择三个两两互不相同的路口 s, cs,c和 ff,分别作为比赛的起点.切换点(运动员在长跑到达这个点后,骑自行车前往终点).终点. 选择一条从 ss出发,经过 cc最终到达 ff的路径.考虑到安全因素,选择的路径经过同一个点至多一次. 在规划路径之前,镇长想请你帮忙计