P4244 [SHOI2008]仙人掌图 II
题目背景
题目这个II是和SHOI2006的仙人掌图区分的,bzoj没有。 但是实际上还是和bzoj1023是一个题目的。
题目描述
如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。显然,仙人图上的每条边,或者是这张仙人图的桥(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,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。
输出格式:
只需输出一个数,这个数表示仙人图的直径长度。
注意直径的定义,距离定义为最小距离,直径定义为最大距离。
然后树边普通dp,环上需要去小距离,把环倍长,每次只留下环的1/2长度的最大值进行更新即可,用单调队列维护一下。
然后我倍长数组没开两倍调了20年...
Code:
#include <cstdio>
#include <algorithm>
using std::min;
using std::max;
const int N=50010;
int head[N],to[N<<4],Next[N<<4],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int n,m,s[N<<1],tot,fa[N],dfn[N],low[N],dfsclock,dp[N],ans,q[N<<1],l,r;
void cal(int rt,int p)
{
int now=p;tot=0;
while(now!=rt)
{
s[++tot]=now;
now=fa[now];
}s[++tot]=rt;
for(int i=1;i<=tot;i++) s[i+tot]=s[i];
tot<<=1,l=1,r=0;
for(int i=1;i<=tot;i++)
{
while(l<=r&&i-q[l]>tot>>2) ++l;
if(l<=r) ans=max(ans,dp[s[i]]+dp[s[q[l]]]+i-q[l]);
while(l<=r&&dp[s[i]]>=dp[s[q[r]]]+i-q[r]) --r;
q[++r]=i;
}
tot>>=1;
for(int i=1;i<=tot;i++) dp[rt]=max(dp[rt],dp[s[i]]+min(i,tot-i));
}
void dfs(int now)
{
dfn[now]=low[now]=++dfsclock;
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=fa[now])
{
if(!dfn[v]) fa[v]=now,dfs(v),low[now]=min(low[now],low[v]);
else low[now]=min(low[now],dfn[v]);
if(dfn[now]<low[v]) ans=max(ans,dp[v]+dp[now]+1),dp[now]=max(dp[now],dp[v]+1);
}
for(int v,i=head[now];i;i=Next[i])
if(fa[v=to[i]]!=now&&dfn[v]>dfn[now])
cal(now,v);
ans=max(ans,dp[now]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int k,u,v,i=1;i<=m;i++)
{
scanf("%d%d",&k,&u);
for(int j=1;j<k;j++)
{
scanf("%d",&v);
add(u,v),add(v,u);
u=v;
}
}
dfs(1);
printf("%d
",ans);
return 0;
}
2018,12,25