前言

有难度的仙人掌题在近几年也只是在国家集训队水平的比赛里才会出现。

不过,这不是说仙人掌对国集水平以下的选手意义不大:

首先,仙人掌暴力 DP 问题难度并不大,在省选、 NOI 甚至 NOIP 中可能出现;

其次,仙人掌问题的处理能很好锻炼选手的特殊情况全面考虑并正确处理的能力,提升对超长代码的把握;

如图所示:

仙人掌图就是长得像仙人掌的图嘛(我真没看出哪里像了)

定义:对一个无向连通图,任意一条边属于至多一个简单环。

桥边:非环边,就是连接环的那些边;

环边:就是环中的边嘛。

在仙人掌上,父亲和儿子都有节点的和环的之分。

DFS 树解决仙人掌 DP 问题

仙人掌的处理是十分复杂的,这里先从简单的 DFS树开始。

树边:DFS 树中存在的边

非树边:DFS 树中不存在的边

大神们还有什么覆盖之类的定义,参考最后的参考文献。

也就是说环是由多条树边和一条非树边组成的,非树边起到了连接的作用。

我们看几道经典题目:

引例
一棵仙人掌,每条边有边权,求 1 号节点到每个节点的最短路径长度。节点个数 n≤105n≤105

我们先 dfs 一遍的到 dfs 树,这是我们之后处理的基础。

然后从一号节点开始 dp 。假设现在已经求出 1 到 u 的距离,枚举 u 的每一个儿子,如果该儿子不在环内,加上边权继续;否则,枚举换上的我们暴力求环上的每个点到 1 的距离,然后在分别从环上的每个点继续 dp 。

[BZOJ1023]cactus仙人掌图[SHOI2008]
一棵带边权仙人掌,求直径(最短路径最长的两个点的最短路径长度)。节点个数

我们 DFS 可以得到一颗 DFS 树。这里的 DFS 与 Tarjan 比较类似(大概就是 Tarjan),对于现在访问的节点 u 我们记录下来 low[u],dfn[u],dpt[u],fa[u] (前两个含义参考 Tarjan ,dpt 是深度(deepth) , fa 是 u 在 DFS 树上的父亲),对于没访问的(即 dfn 为 0 的)节点继续递归,向上回溯时更新 low 值。

遍历每条边时如果 low[v] 大于 dfn[u] ,说明此边为树边;对于一个节点,若其某个儿子在 DFS 树上的父亲不是它(没错,它的儿子的父亲不是它,机房的小伙伴们都笑疯了),那么说明这里出现了一个环,且此节点为环的根(深度最小的节点),它的那个儿子为环的尾(环中最后被遍历到的,也就是 DFS 树的叶子节点)

我们做以上这些的目的主要就是判断环和桥,然后分别 DP 处理。

我们定义f[i] 为以 i 为端点的最长链的长度。

对于桥来说十分简单(此时假设没有环):

这里我们在代码中的写法稍有不同:

‍‍‍‍‍‍先更新 ans ,后更新 f[u]。

遍历所有儿子,对每个儿子,f[u] 中存的可能是(我们只考虑是的情况,因为只有此时才对结果有影响,这也是为什么上一条要确保) second_max(f[v]) ,而 f[v] 可能是(依旧只考虑是的情况) max(f[v]),所以我们只需用 f[u]+f[v]+1 来更新 ans 即可;‍‍‍‍‍‍

现在我们考虑环的问题:

对于一个环,我们的宗旨是把它缩成一个点(即把一个环的 f 信息都存在其根上),然后就可以开心地按之前的方式 DP 啦!

由于环中更新答案的时候只转一圈不能保证答案最优(因为有可能最优的那一部分环被根分开了),又由于环中距离的定义是最短路,所以我们只要转够一圈半即可。

如图:

定义环的节点集合为 C ,记环中一点 a 在环中的遍历顺序为 aid 、环长(环中节点个数)为 L ,则在环中两个点之间的距离

我们记一个环的根为 x 、尾为 y。

‍‍实现的时候,我们把环中的节点的 f 依次存在一个数组里,然后将这个数组倍长(就是将其复制一遍放到尾部),遍历时动一定一(这里莫名怀念小晓笑潇),若之间相差大于 L2 就 continue,再用单调队列优化一下, ans 就能轻松更新好了。之后再按照公式更新 f[x] 即可。

整个算法过程的时间复杂度仅为 O(N),还是蛮快的。‍‍

#include

#include

using namespace std;

const int MAXN=5e4+5,MAXM=MAXN<>

int n,m;

struct E{int next,to;} e[MAXM<1];int>

void addEdge(int u,int v)

{

e[++ecnt]=(E){G[u],v};G[u]=ecnt;e[++ecnt]=(E){G[v],u};G[v]=ecnt;

}

int ans;

int f[MAXN];

int dfn[MAXN],dcnt,low[MAXN],dpt[MAXN],fa[MAXN];

int que[MAXN<><>

void solve(int x,int y)

{

int cnt=dpt[y]-dpt[x]+1,head=1,tail=1,i;for(i=y;i!=x;i=fa[i]) a[cnt--]=f[i];a[1]=f[x];cnt=dpt[y]-dpt[x]+1;for(i=1;i<=cnt;i++) a[i+cnt]="">que[1]=1;for(i=2;i<=cnt+(cnt>>1);i++){if(i-que[head]>(cnt>>1)) head++;ans=max(ans,a[i]+i+a[que[head]]-que[head]);while(head<=tail&&a[i]-i>=a[que[tail]]-que[tail]) tail--;que[++tail]=i;}for(i=2;i<=cnt;i++) f[x]="">

}

void dfs(int u)

{

int i;dfn[u]=low[u]=++dcnt;for(i=G[u];i;i=e[i].next){int v=e[i].to;if(v==fa[u]) continue;if(!dfn[v]) {fa[v]=u;dpt[v]=dpt[u]+1;dfs(v);}low[u]=min(low[u],low[v]);if(low[v]>dfn[u]) ans=max(ans,f[u]+f[v]+1),f[u]=max(f[u],f[v]+1);//对树边的更新}for(i=G[u];i;i=e[i].next){int v=e[i].to;if(fa[v]!=u&&dfn[u]solve(u,v);}

}

int main()

{

int i,j;scanf('%d%d',&n,&m);for(i=1;i<>{int k;scanf('%d',&k);int u,v;scanf('%d',&u);for(j=2;j<=k;j++) scanf('%d',&v),addedge(u,v),u="">}dfs(1);printf('%d\n',ans);return 0;

}

圆方树

通过前面的几道例题,我们发现:其实解决仙人掌 DP 问题不过就是参照树上的解法然后对环上的情况特殊处理一下,把环的信息记录到一个点上。

其实,神犇们很早就发现了这一点,于是他们想:既然仙人掌的许多问题在树上都有现成的解法,那么如果直接把仙人掌变成树,岂不美哉?

于是,神犇们成功的仙人掌变成树,并给这种树起了一个生动形象的名字:圆方树,它能解决大多数静态仙人掌问题

定义

构造

从任意一个点跑 Tarjan 求点双连通分量;

对于每个点双,从栈中取出,这时栈中的顺序就是环上的顺序,在圆方树中建立方点,依次向栈中的圆点连边;

如果这条边是桥边,我们直接在圆方树中加入它。

性质

两个方点不会相连

圆方树是无根树

子仙人掌:以 r 为根的仙人掌上的点 p 的子仙人掌是从仙人掌中起吊 p 到 r 的简单路径上的所有边后, p 所在的连通块。

以 r 为根的仙人掌中点 p 的子仙人掌就是圆方树以 r 为根时点 p 的子树中的所有圆点。

[BZOJ4316] 小 C 的独立集

在一个无向连通图中选出若干个点,这些点互相没有边连接,并使取出的点尽量多。数据保证图的一条边属于且仅属于一个简单环,图中没有重边和自环。点数

在一个无向连通图中选出若干个点,这些点互相没有边连接,并使取出的点尽量多。数据保证图的一条边属于且仅属于一个简单环,图中没有重边和自环。点数 n ≤ 1 0 6 n\leq10^6 n≤106 ,边数 m ≤ 1 0 6 m\leq10^6 m≤106
类比树上的解法:设f[i][0/1] 表示点 i 是否选时子树内的最大独立集;

如果一条边连接两个圆点,用树上转移方式即可;

而对于连接圆点和方点的情况,把这个换中所有点拿出来,跑一个环上的 DP 。

时间复杂度: O(n)

其实我们在解这道题的时候,没有必要真正建出圆方树,我在代码里建出圆方树只是为了举例说明,是为让大家熟悉圆方树的建法。

#include

#include

#include

const int MAXN=2e5+5,MAXM=2e5+5,INF=~0U>>1;

int n,m,newn;//newn:圆方树的点数

struct CFS

{

struct E{int next,to;} e[MAXM];int ecnt,G[MAXN];void addEdge(int u,int v){e[++ecnt]=(E){G[u],v};G[u]=ecnt;}void addEdge2(int u,int v){addEdge(u,v);addEdge(v,u);}CFS(){ecnt=1;}

} G,T;

int f[MAXN][2],g[MAXN][2],gcnt;

void treeDP(int u,int from)

{

int i;if(u<>{f[u][0]=0;f[u][1]=1;for(i=T.G[u];i;i=T.e[i].next){int v=T.e[i].to;if(v==from) continue;treeDP(v,u);if(v>n) continue;f[u][0]+=std::max(f[v][0],f[v][1]);f[u][1]+=f[v][0];}}else{for(i=T.G[u];i;i=T.e[i].next)if(T.e[i].to!=from)treeDP(T.e[i].to,u);gcnt=0;for(i=T.G[u];i;i=T.e[i].next){g[++gcnt][0]=f[T.e[i].to][0];g[gcnt][1]=f[T.e[i].to][1];}for(i=gcnt-1;i;i--){g[i][0]+=std::max(g[i+1][0],g[i+1][1]);g[i][1]+=g[i+1][0];}f[from][0]=g[1][0];gcnt=0;for(i=T.G[u];i;i=T.e[i].next){g[++gcnt][0]=f[T.e[i].to][0];g[gcnt][1]=f[T.e[i].to][1];}g[gcnt][1]=-INF;for(i=gcnt-1;i;i--){g[i][0]+=std::max(g[i+1][0],g[i+1][1]);g[i][1]+=g[i+1][0];}f[from][1]=g[1][1];}

}

int fa[MAXN],dfn[MAXN],dcnt;

bool onRing[MAXN];

void dfs(int u,int la)

{

int i,j;dfn[u]=++dcnt;for(i=G.G[u];i;i=G.e[i].next){int v=G.e[i].to;if(v==la) continue;if(!dfn[v]){fa[v]=u;onRing[u]=false;dfs(v,u);if(!onRing[u]) T.addEdge2(u,v);}else{if(dfn[v]>dfn[u]) continue;for(j=u,++newn;j!=fa[v];j=fa[j])T.addEdge2(newn,j),onRing[j]=true;}}

}

int main()

{

int i,u,v;scanf('%d%d',&n,&m);newn=n;for(i=1;i<>{scanf('%d%d',&u,&v);G.addEdge2(u,v);}dfs(1,0);treeDP(1,0);printf('%d\n',std::max(f[1][0],f[1][1]));

}
来源:
http://www.360doc.com/content/19/0421/12/63590853_830310554.shtml

仙人掌相关问题的解法(1)-DFS树解决仙人掌DP问题,圆方树相关推荐

  1. [JZOJ 5909] [NOIP2018模拟10.16] 跑商(paoshang) 解题报告 (圆方树)

    题目链接: https://jzoj.net/senior/#contest/show/2529/2 题目: 题目背景: 尊者神高达很穷,所以他需要跑商来赚钱 题目描述: 基三的地图可以看做 n 个城 ...

  2. 【luogu P4320】道路相遇(圆方树)

    道路相遇 题目链接:luogu P4320 题目大意 给你一个无向连通图,无重边自环,然后每次给你两点,问你有多少个点是两点间路径必有的. 思路 圆方树pre模板题? 圆方树怎么做这里不说,看铁人两项 ...

  3. 仙人掌圆方树学习笔记

    终于对仙人掌有了一点初步的理解. 仙人掌 仙人掌是什么? 仙人掌是一个无向图. 仙人掌有什么特点? 仙人掌的每条边只属于一个简单环. 下面是一个栗子 有什么用呢? 我们可以先用\(tarjan\)找出 ...

  4. bzoj4564: [Haoi2016]地图 仙人掌的圆方树 莫队 分块

    bzoj4564: [Haoi2016]地图 Description 一天rin来到了一个遥远的都市.这个都市有n个建筑,编号从1到n,其中市中心编号为1,这个都市有m条双向通 行的街道,每条街道连接 ...

  5. P5236 【模板】静态仙人掌(仙人掌圆方树)

    无向仙人掌图 一般需要重构成 仙人掌有向树 然后我们 就考虑 圆点的 方点的不同讨论 这个题是查询两点间最短路 如果lca 是圆点 那么就是 d[a]+d[b]-2*d[lca] 如果是方点 我们需要 ...

  6. cactus仙人掌图【仙人掌圆方树+树形DP+单调队列】

    题目链接 BZOJ 1023 首先,圆方树是比较好想到的,维护直径,我们最方便的做法就是先让它变成一棵树,这里因为是仙人掌图,所以就用圆方树来构建. 再者,就是维护直径了,比较好想到的是非环上结点,就 ...

  7. 仙人掌与圆方树的学习 【模板】静态仙人掌

    题目链接 BZOJ 2125 最短路 圆方树 求一幅仙人掌图中,Q次询问两点最短路. 仙人掌问题,我们可以直接将原来的N个点缩点成为一棵生成树--圆方树. 这棵圆方树是怎样建立的呢,首先,我们看图: ...

  8. 洛谷 :P5236 【模板】静态仙人掌(圆方树模板 + 仙人掌最短路)

    题意很简单,在仙人掌图上求两点的最短路. 做法:需要用到圆方树 先来看看什么是圆方树:圆方树,就是由仙人掌图转化而来,树上分两种点:圆点和方点,圆点是仙人掌图上的点,方点是由仙人掌的环转化而来. 由于 ...

  9. 仙人掌问题(圆方树)

    [算法简介] 仙人掌就是把树上多连了一些返祖边,构成了一些环 根据仙人掌这个名字我们也可以较为形象的感受到图的形态 具体的,仙人掌分为点仙人掌和边仙人掌,定义分别为点/边最多属于一个环 之所以把这样的 ...

最新文章

  1. json_encode 中文不乱码
  2. 9种设计模式在Spring中的运用,一定要非常熟练!
  3. Linux 运维工作中的经典应用ansible(批量管理)Docker容器技术(环境的快速搭建)...
  4. global.php,global.php
  5. 关于LRU缓存简单记录以及代码补全。
  6. Linux通过RPM方式指定软件安装目录
  7. java jar合并_多个jar包合并成一个jar包(ant)
  8. 开源WEB服务器-lighttpd 1.4.24发布
  9. app用http3与服务器_mqtt服务器搭建以及客户端Paho安装使用
  10. java游戏毕业论文参考文献全面论文100个
  11. BT种子下载软件uTorrent Pro v3.5.5.45972
  12. 朗文当代高级英语辞典android,朗文当代高级英语词典下载
  13. 面试时会问到的项目中的问题总汇
  14. HTML+CSS大作业——水果介绍-橙子之家(6页) HTML+CSS+JavaScript 学生dreamweaver网页设计作业成品
  15. java 上传文件 md5_上传到文件选择器时的md5校验和
  16. vscode 脑图插件mindmap
  17. 树莓派51/100 - Pico下用MicroPython在ssd1306上显示汉字
  18. 设置 Scite编辑器的默认编辑为 UTF-8 ,及其他一些参数配置
  19. druid之本机批量摄取
  20. TemporalType

热门文章

  1. 前端扑街仔的nginx配置
  2. 【C语言】指针:输入某一年的第几天,计算并输出它是这一年的第几月第几天
  3. 每个程序员1小时内必须解决的5个编程问题
  4. android更改刷机代码,Android刷机教程(示例代码)
  5. 李彦宏:一个人的百度
  6. 大一过计算机考试题,大一计算机考试题(含答案)
  7. 华擎、映泰主板的网络唤醒(WOL)实例
  8. 借助faker+pandas向excel表格里制造测试数据
  9. 开奖计算---五星直选复式
  10. IAR中Error[Pe007]: unrecognized token