cactus仙人掌图

题目描述

如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。

举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路:(4,3,2,1,6,5,4)、(7,8,9,10,2,3,7)以及(4,3,7,8,9,10,2,1,6,5,4),而(2,3)同时出现在前两个的简单回路里。另外,第三张图也不是仙人图,因为它并不是连通图。显然,仙人图上的每条边,或者是这张仙人图的桥(bridge),或者在且仅在一个简单回路里,两者必居其一。定义在图上两点之间的距离为这两点之间最短路径的距离。定义一个图的直径为这张图相距最远的两个点的距离。现在我们假定仙人图的每条边的权值都是1,你的任务是求出给定的仙人图的直径

输入格式

输入的第一行包括两个整数n和m(1≤n≤50000以及0≤m≤10000)。其中n代表顶点个数,我们约定图中的顶点将从1到n编号。接下来一共有m行。代表m条路径。每行的开始有一个整数k(2≤k≤1000),代表在这条路径上的顶点个数。接下来是k个1到n之间的整数,分别对应了一个顶点,相邻的顶点表示存在一条连接这两个顶点的边。一条路径上可能通过一个顶点好几次,比如对于第一个样例,第一条路径从3经过8,又从8返回到了3,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。

输出格式

只需输出一个数,这个数表示仙人图的直径长度。

输入输出样例

输入 #1
复制

15 3
9 1 2 3 4 5 6 7 8 3
7 2 9 10 11 12 13 10
5 2 14 9 15 10
输出 #1
复制

8
输入 #2
复制

10 1
10 1 2 3 4 5 6 7 8 9 10
输出 #2
复制

9

说明/提示


对第一个样例的说明:如图,6号点和12号点的最短路径长度为8,所以这张图的直径为8。

【注意】使用Pascal语言的选手请注意:你的程序在处理大数据的时候可能会出现栈溢出。
如果需要调整栈空间的大小,可以在程序的开头填加一句:{$M 5000000},其中5000000即指代栈空间的大小,请根据自己的程序选择适当的数值。

题解

参见王逸松论文《仙人掌相关算法及其应用》。

1.2 仙人掌的边数

对于一棵n个结点的仙人掌,我们考虑它的一棵生成树,有n − 1条边。剩下的边均为非树边,每条非树边对应了生成树上的一条路径,并形成了一个简单环。由于每一条边最多属于一个简单环,任意两条非树边对应的路径的边的交集为空,即这些路径不重叠。因此最多有n − 1条这样的路径,所以n个结点的仙人掌最多有2n − 2条边,最少有n − 1条边。

1.3.1 父亲

类比树上结点的父亲,我们定义仙人掌上结点的父亲和环的父亲。

如果一个结点u到仙人掌的根之间的所有简单路径的第一条边相同,那么就类似树上的情况,u的父亲为u到根的一条简单路径上第二个点,否则u的父亲为u到根的一条简单路径上第一条边所在的环。

1.3.2 儿子

类比树上结点的儿子,我们定义仙人掌上结点的儿子和环的儿子。

一个结点的儿子可能是个结点,也可能是个环。

一个环的儿子为环上除去环的父亲以外的所有结点。

1.3.3 父亲结点和母亲结点

对于一个在环上的结点,我们定义它的父亲结点和母亲结点分别为环上与它相邻的两个结点。(请注意区分父亲和父亲结点)

从环的任意一个儿子出发,沿着父亲结点和母亲结点就能遍历整个环。

1.4 如何遍历一棵仙人掌

类似遍历一棵树的过程,我们从根开始dfs,假设当前在结点x,接下来要访问结点y。

如果y还没访问过,就跟树上的情况一样处理,只要将y的父亲设置为x即可。

如果y已经访问过,且第一次访问y的时间早于第一次访问x的时间,那么说明我们发现了一个环,这个环的父亲为y,儿子为x到y路径上除去y的所有点。我们遍历这个环,并设置环上所有点的父亲结点和母亲结点就行了。

如果y已经访问过,且第一次访问y的时间晚于第一次访问x的时间,那么说明y在一个已经访问过的环上,可以无视这种情况。

仙人掌上的DP

类比树上的DP,我们可以在仙人掌上进行DP。

从仙人掌的根开始DP,假设当前DP到结点u,我们先处理结点u上的信息,然后枚举u的每一个儿子和以u为父亲的每一个环的每个儿子进行DP。

考虑树形DP。我们可以对每个x求出以x为根的子树的最大深度,然后用每个结点的不同儿子的最大深度+次大深度更新答案即可。

对于仙人掌上的情况,可以类比树形DP。我们先dfs一遍弄清仙人掌的结构,然后对于每个结点x,求出子仙人掌x的最大深度。(子仙人掌x的定义是,删掉根到x的所有简单路径上的边之后,x所在的连通块)

对于每个结点,用它的不同儿子的最大深度+次大深度来更新答案。

对于每个环,还要用环上的每对结点的最大深度的和加上这对结点的最短路长度来更新答案。不妨枚举其中一个结点,那么最短路是顺时针走的另一个结点,一定是环上的一个区间,而且这个区间的另一个端点随着枚举的结点顺时针移动而顺时针移动。这是个两个端点都单调移动的RMQ问题,可以用单调队列来维护。


实际上,仙人掌问题可以采用更为现代的科技:圆方树。

圆点是原来的节点,方点环所对应的点,他们的拓扑关系其实就是父亲儿子的定义。

考虑转化与化归的思想,多叉树→二叉树→多叉树,先抽象再还原。这里的仙人掌→圆方树→仙人掌也是一样的。与左儿子-右兄弟表示法一样,圆方树对应着更新的拓扑关系。

把圆方树建出来做就行了。

时间复杂度\(O(n)\).

co int N=100000+10;
int n;
// originial tree
vector<int> e[N];
int pos[N],dfn;
// round square tree
int cir,len[N];
int fa[N],dep[N],to[N],nx[N];
int f[N],ans; void tarjan(int x,int fa){
static int st[N],top;
st[++top]=x;
pos[x]=++dfn;
for(unsigned i=0;i<e[x].size();++i){
int y=e[x][i];
if(y==fa) continue;
if(!pos[y]){
::fa[y]=x,dep[y]=dep[x]+1;
tarjan(y,x);
}
else if(pos[y]<pos[x]){
len[++cir]=dep[x]-dep[y]+1;
::fa[n+cir]=y,nx[n+cir]=to[y],to[y]=n+cir;
for(int j=top;st[j]!=y;--j)
::fa[st[j]]=n+cir,nx[st[j]]=to[n+cir],to[n+cir]=st[j];
}
}
for(unsigned i=0;i<e[x].size();++i){
int y=e[x][i];
if(::fa[y]!=x) continue;
nx[y]=to[x],to[x]=y;
}
--top;
}
void dp(int x){
if(x<=n){ // round
for(int y=to[x];y;y=nx[y]){
dp(y);
ans=max(ans,f[x]+f[y]+1);
f[x]=max(f[x],f[y]+1);
}
return;
}
// square
for(int y=to[x];y;y=nx[y]) dp(y);
static int tmp[N],tot;
tot=0;
for(int y=to[x];y;y=nx[y])
tmp[++tot]=y; // notice the order
assert(tot+1==len[x-n]);
copy(tmp+1,tmp+tot+1,tmp+tot+1);
static int qu[N],l,r;
l=1,r=0;
for(int i=1;i<=tot<<1;++i){
while(l<=r&&i-qu[l]>(tot+1)>>1) ++l; // tot+1 -> ::fa[x]
if(l<=r) ans=max(ans,f[tmp[i]]+f[tmp[qu[l]]]+i-qu[l]);
while(l<=r&&f[tmp[i]]-i>f[tmp[qu[r]]]-qu[r]) --r;
qu[++r]=i;
}
for(int y=to[x];y;y=nx[y])
f[x]=max(f[x],f[y]+min(dep[y]-dep[::fa[x]],len[x-n]-(dep[y]-dep[::fa[x]]))-1); // correspondence with f
}
int main(){
read(n);
for(int m=read<int>();m--;){
int k=read<int>()-1;
for(int x=read<int>(),y;k--;x=y){
read(y);
e[x].push_back(y),e[y].push_back(x);
}
}
tarjan(1,0);
dp(1);
printf("%d\n",ans);
return 0;
}

#87. mx的仙人掌

如果一个无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过重复的结点的环。

现给定一棵仙人掌,每条边有一个正整数权值,每次给 $k$ 个点(可以存在相同点),问从它们中选出两个点(可以相同),它们之间最短路的最大值是多少。

输入格式

第一行两个非负整数 $n, m$,表示仙人掌的点数和边数。

接下来 $m$ 行,每行三个正整数 $v, u, w$ $(1 \le v, u \le n)$,表示 $v$ 与 $u$ 之间有一条边权为 $w$ 的无向边。点从 $1$ 开始编号。

保证输入的图是一棵仙人掌,保证没有自环,但可能有重边。

接下来一行一个非负整数 $Q$,表示询问个数。

接下来 $Q$ 行每行第一个数是正整数 $\mathrm{cnt}$ 表示点数,接下来 $\mathrm{cnt}$ 个数表示给定的点。

输出格式

对每个询问输出一个数,表示该询问对应的最大值。

样例一

input

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

output

11
20
25
27
19
0

explanation

前五个询问的答案路径分别为(如果有重边则显然走较短的边):

$9 \rightarrow 8 \rightarrow 5$

$8 \rightarrow 9 \rightarrow 7 \rightarrow 10$

$8 \rightarrow 9 \rightarrow 7 \rightarrow 1 \rightarrow 6$

$4 \rightarrow 8 \rightarrow 9 \rightarrow 7 \rightarrow 1 \rightarrow 6$

$2 \rightarrow 9 \rightarrow 8 \rightarrow 4$

最后一个询问的答案显然是$0$。

限制与约定

边权不超过 $2 ^ {31} - 1$。

$\mathrm{tot}$ 表示询问的总点数。

对于 5% 的数据,$n, \mathrm{tot} \le 7$。

对于 10% 的数据,$n, \mathrm{tot} \le 5000$。

对于另外 10% 的数据,$Q \le 10$,其中存在 $Q \le 2$ 的数据。

对于另外 30% 的数据,$m = n - 1$。

对于 100% 的数据,$n, \mathrm{tot} \le 300000$。

此外为了照顾被卡常数的同学,本题存在过渡数据。

时间限制:$5\texttt{s}$

空间限制:$512\texttt{MB}$

题解

看题面,猜想有虚仙人掌这种算法,的确有。

用虚圆方树还原虚仙人掌即可。当然不用把虚仙人掌建出来,因为反正DP是在圆方树上进行的,所以直接在虚圆方树上DP即可。

把圆点连向方点的边权设为圆点到方点的父亲的最短距离。用树上倍增来维护,因为不仅要求lca和距离,还需要在方点DP时找到圆点在环上的祖先来做单调队列DP。

学到了:unique的自定义比较函数是判断相等的,而不是判断小于的。

时间复杂度\(O(n\log n)\)。


吐槽:有重边,我没看到……询问点有重点,我没看到……于是写了一天。

仙人掌有重边是正常的,以后写题的时候要注意处理方法。这道题我选择去掉重边,但是不去掉相当于一个环也是可以处理的。

co int N=(int)6e5+10;

int n;
vector<pair<int,int> > e[N];
int pos[N],dfn;
int cir;LL len[N];
int fa[N];LL dfs_dep[N];
int nx[N],to[N];LL we[N]; void tarjan(int x,int fa){
static int st[N],top;
st[++top]=x;
pos[x]=++dfn;
for(unsigned i=0;i<e[x].size();++i){
int y=e[x][i].first,w=e[x][i].second;
if(y==fa) continue;
if(!pos[y]){
::fa[y]=x,dfs_dep[y]=dfs_dep[x]+w;
tarjan(y,x);
}
else if(pos[y]<pos[x]){
len[++cir]=dfs_dep[x]-dfs_dep[y]+w;
::fa[cir]=y;
nx[cir]=to[y],to[y]=cir;
for(int j=top;st[j]!=y;--j){
::fa[st[j]]=cir;
nx[st[j]]=to[cir],to[cir]=st[j];
we[st[j]]=min(dfs_dep[st[j]]-dfs_dep[y],len[cir]-(dfs_dep[st[j]]-dfs_dep[y]));
}
}
}
for(unsigned i=0;i<e[x].size();++i){
int y=e[x][i].first;
if(::fa[y]!=x) continue;
nx[y]=to[x],to[x]=y,we[y]=e[x][i].second;
}
--top;
} LL dis[N];int dep[N];
int anc[N][20],lg[N]; void pretreat(int x){
pos[x]=++dfn;
anc[x][0]=fa[x];
for(int i=1;i<=lg[cir];++i) anc[x][i]=anc[anc[x][i-1]][i-1];
for(int y=to[x];y;y=nx[y]){
dis[y]=dis[x]+we[y],dep[y]=dep[x]+1;
pretreat(y);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=anc[x][lg[dep[x]-dep[y]]];
if(x==y) return x;
for(int i=lg[dep[x]];i>=0;--i)
if(anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
return anc[x][0];
}
il LL get_dis(int x,int y){
int lca=::lca(x,y);
return dis[x]+dis[y]-2*dis[lca];
}
int reach(int y,int x){
while(dep[y]>dep[x]+1) y=anc[y][lg[dep[y]-dep[x]-1]];
return y;
} namespace T{ // vitual tree
int cnt,a[N];
int nx[N],to[N];LL we[N];
LL f[N],ans; struct node {LL f,dep;};
il bool operator<(co node&a,co node&b){
return a.dep<b.dep;
}
void dp(int x){
if(x<=n){
for(int y=to[x];y;y=nx[y]){
dp(y);
ans=max(ans,f[x]+f[y]+we[y]);
f[x]=max(f[x],f[y]+we[y]);
}
return;
}
for(int y=to[x];y;y=nx[y]) dp(y);
static node p[N<<1];
int tot=0;
for(int y=to[x];y;y=nx[y]){
int z=reach(y,x);
p[++tot].f=f[y]+get_dis(z,y),p[tot].dep=dfs_dep[z];
}
sort(p+1,p+tot+1);
for(int i=tot+1;i<=tot<<1;++i)
p[i]=p[i-tot],p[i].dep+=len[x];
static int qu[N<<1];
int l=1,r=0;
for(int i=1;i<=tot<<1;++i){
while(l<=r&&p[i].dep-p[qu[l]].dep>len[x]>>1) ++l;
if(l<=r) ans=max(ans,p[qu[l]].f+p[i].f+p[i].dep-p[qu[l]].dep);
while(l<=r&&p[i].f-p[i].dep>=p[qu[r]].f-p[qu[r]].dep) --r;
qu[++r]=i;
}
for(int i=1;i<=tot;++i)
f[x]=max(f[x],p[i].f+min(p[i].dep-dfs_dep[fa[x]],len[x]-(p[i].dep-dfs_dep[fa[x]])));
} il bool cmp(int a,int b){
return pos[a]<pos[b];
}
il bool equ(int a,int b){
return pos[a]==pos[b];
}
void solve(){
read(cnt);
for(int i=1;i<=cnt;++i) read(a[i]);
sort(a+1,a+cnt+1,cmp);
cnt=unique(a+1,a+cnt+1,equ)-a-1; // edit 2: mutivertex
static int st[N],top;
st[top=1]=a[1];
for(int i=2,end=cnt;i<=end;++i){
int lca=::lca(a[i],st[top]);
for(;top>1&&pos[st[top-1]]>=pos[lca];--top)
nx[st[top]]=to[st[top-1]],to[st[top-1]]=st[top],we[st[top]]=get_dis(st[top-1],st[top]);
if(lca!=st[top]){
a[++cnt]=lca;
nx[st[top]]=to[lca],to[lca]=st[top],we[st[top]]=get_dis(lca,st[top]);
st[top]=lca;
}
st[++top]=a[i];
}
for(;top>1;--top)
nx[st[top]]=to[st[top-1]],to[st[top-1]]=st[top],we[st[top]]=get_dis(st[top-1],st[top]);
dp(st[top]);
printf("%lld\n",ans);
for(int i=1;i<=cnt;++i)
nx[a[i]]=to[a[i]]=we[a[i]]=f[a[i]]=0;
ans=0;
}
} struct edge{int x,y,w;};
il bool operator<(co edge&a,co edge&b){
return a.x!=b.x?a.x<b.x:(a.y!=b.y?a.y<b.y:a.w<b.w);
}
int main(){
cir=read(n);int m=read<int>();
static edge oe[N]; // edit 1: multiedge
for(int i=1;i<=m;++i){
read(oe[i].x),read(oe[i].y),read(oe[i].w);
if(oe[i].x>oe[i].y) swap(oe[i].x,oe[i].y);
}
sort(oe+1,oe+m+1);
for(int i=1;i<=m;++i){
if(oe[i].x==oe[i-1].x&&oe[i].y==oe[i-1].y) continue;
e[oe[i].x].push_back(mp(oe[i].y,oe[i].w)),e[oe[i].y].push_back(mp(oe[i].x,oe[i].w));
}
tarjan(1,0);
lg[0]=-1;
for(int i=1;i<=cir;++i) lg[i]=lg[i>>1]+1;
dfn=0,pretreat(1);
for(int q=read<int>();q--;) T::solve();
return 0;
}

SHOI2008 cactus仙人掌图 和 UOJ87 mx的仙人掌的更多相关文章

  1. 【bzoj1023】仙人掌图

    [bzoj1023]仙人掌图 题意 给一棵仙人掌,求直径. \(n\leq 100000\) 分析 分析1:[Tarjan]+[环处理+单调队列优化线性dp]+[树形dp] 分开两种情况处理: ①环: ...

  2. 【BZOJ】【1023】【SHOI2008】cactus仙人掌图

    DP+单调队列/仙人掌 题解:http://hzwer.com/4645.html->http://z55250825.blog.163.com/blog/static/150230809201 ...

  3. bzoj 1023: [SHOI2008]cactus仙人掌图 tarjan缩环&&环上单调队列

    1023: [SHOI2008]cactus仙人掌图 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 1141  Solved: 435[Submit][ ...

  4. bzoj千题计划113:bzoj1023: [SHOI2008]cactus仙人掌图

    http://www.lydsy.com/JudgeOnline/problem.php?id=1023 dp[x] 表示以x为端点的最长链 子节点与x不在同一个环上,那就是两条最长半链长度 子节点与 ...

  5. 1023: [SHOI2008]cactus仙人掌图 - BZOJ

    Description如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人图(cactus).所谓简单回路就是指在图上不重复经过任何一个顶点的回路 ...

  6. [BZOJ]1023 cactus仙人掌图(SHOI2008)

    NOIP后的第一次更新嗯. Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus).所谓简单回路就是指在 ...

  7. BZOJ1023:[SHOI2008]cactus仙人掌图(圆方树,DP,单调队列)

    Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus). 所谓简单回路就是指在图上不重复经过任何一个顶点 ...

  8. 【刷题】BZOJ 1023 [SHOI2008]cactus仙人掌图

    Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus).所谓简单回路就是指在图上不重复经过任何一个顶点的 ...

  9. BZOJ1023: [SHOI2008]cactus仙人掌图(仙人掌dp)

    Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 3467  Solved: 1438[Submit][Status][Discuss] Descripti ...

随机推荐

  1. 怎么又出错了?盘点java中最容易出现的错误

    现如今,java已经广泛应用各种软件开发领域.基于面向对象的设计,java屏蔽了诸如C,C++等语言的一些复杂性,提供了垃圾回收机制,平台无关的虚拟机技术,Java创造了一种前所未有的开发方式.所以, ...

  2. [转] ABP框架Web API跨域问题的解决方案

    原文地址:​https://www.cnblogs.com/farb/p/ABPWebAPICrossDomain.html 1.在Web Api 项目下安装 Microsoft.AspNet.Web ...

  3. Linux Ubntu 常用命令

    1.  ctrl+alt+t  打开一个终端命令窗口. 2.  在一个文件夹下 按ctrl+H,会显示以. 开头的文件. 3.  同时打开多个文件夹浏览窗口:在文件夹窗口中 Ctrl + N 4.  ...

  4. c++ 基础学习(二)—— IO 对象

    1. IO 类 c++ 语言不能直接处理输入输出,而是通过一族定义在标准库中的类型来处理 IO,这些类型支持设备读取数据,向设备写入数据的 IO 的数据操作. istream 输入流类型,提供输入操作 ...

  5. Go语言 ( 切片)

    本文主要介绍Go语言中切片(slice)及它的基本使用. 引子 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性. 例如: func arraySum(x []int) in ...

  6. grafana部署安装

    部署grafana 在prometheus& grafana server节点部署grafana服务. 1. 下载&安装 # 下载 [root@prometheus ~]# cd /u ...

  7. Python之TensorFlow的数据的读取与存储-2

    一.我们都知道Python由于GIL的原因导致多线程并不是真正意义上的多线程.但是TensorFlow在做多线程使用的时候是吧GIL锁放开了的.所以TensorFlow是真正意义上的多线程.这里我们主 ...

  8. ABP 基于DDD的.NET开发框架 学习(六)创建新动态Api

    我们想要这个服务暴露成一个Web API控制器,以方便客户端调用.ASP.NET Boilerplate能够自动且动态地为这个应用服务创建Web API 控制器,只需要一行配置代码即可完成. Dyna ...

  9. C# vb .net实现高斯模糊

    在.net中,如何简单快捷地实现Photoshop滤镜组中的高斯模糊效果呢?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码: 设置授权 第 ...

  10. python超链接抓取工具

    python实现自动抓取某站点内所有超链接 (仅供学习使用) 代码部分 #!/usr/bin/python import requests import time import re import s ...