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. C++容器简介1—stack

    一.简介 stack是一种容器适配器(STL的容器分为顺序容器和关联容器,容器适配器),被设计来用于操作先进后出(FILO)结构的情景,在这种情况下, 元素的插入和删除都只能在容器的尾部进行. sta ...

  2. Django之拾遗

    一.设计模式 1.1 MVC 模型(M)是数据的表述,非真正数据,而是数据接口. 视图(V)是你看到的界面,是模型的表现层,此外还提供了收集用户输入的接口. 控制器(C)控制模型和视图之间的信息流动. ...

  3. SPI通讯(Serial Peripheral interface)

    1. SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线:SCLK,MISO,MOSI,CS 2. SPI结构简图: 可以看出,SPI主从设备两端都有一个位移寄存器,数据在位 ...

  4. windows 开始→运行→命令集锦

    windows 开始→运行→命令集锦 来源于网络,侵权请通知我删除 命令 说明 vwinver 检查Windows版本 wmimgmt.msc 打开windows管理体系结构(WMI) wupdmgr ...

  5. Python 获取本月的最后一天

    一.需求 现在有一个场景,需要每月的最后一天,发送一封邮件. 二.获取本月最后一天 有没有办法使用Python的标准库轻松确定(即一个函数调用)给定月份的最后一天? 答案是有的,使用 datetime ...

  6. 传统IDC 部署网站

    选择IDC机房 1.选择云主机. 2.传统IDC a购买服务器 b服务器托管 c装系统 装系统 虚拟机软件 vmware workstation virtualbox hyper-v 下载:r.ami ...

  7. 【洛谷 P5017】 摆渡车(斜率优化)

    题目链接 算是巩固了一下斜率优化吧. 设\(f[i]\)表示前\(i\)分钟最少等待时间. 则有\(f[i]=\min_{j=0}^{i-m}f[j]+(cnt[i]-cnt[j])*i-(sum[i ...

  8. springboot activiti 工作流版本 集成代码生成器 shiro 安全框架

    官网:www.fhadmin.org 工作流模块---------------------------------------------------------------------------- ...

  9. ES6- - Map与Set

    Map和Set 是 ES6 中新增的一种数据结构.Map为类似于Object的键值对结构,Set为成员唯一的类数组结构.以Map为例介绍两种数据结构的遍历方法.for...of var map = n ...

  10. php生成一维码以及保存-转载

    地址:http://www.cnblogs.com/ForEvErNoME/archive/2012/04/21/2460944.html 注释掉: //header('Content-Type: i ...