bzoj1023 [SHOI2008]cactus仙人掌图

1023: [SHOI2008]cactus仙人掌图

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 3174  Solved: 1312
[Submit][Status][Discuss]

Description

  如果某个无向连通图的任意一条边至多只出现在一条简单回路(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
,你的任务是求出给定的仙人图的直径。

 

Input

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

Output

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

Sample Input

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 8
10 1
10 1 2 3 4 5 6 7 8 9 10

Sample Output

8
9

HINT

 

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


 


【注意】使用Pascal语言的选手请注意:你的程序在处理大数据的时候可能会出现栈溢出。

如果需要调整栈空间的大小,可以在程序的开头填加一句:{$M 5000000},其中5000000即

指代栈空间的大小,请根据自己的程序选择适当的数值。

分析:比较难的一道题.

          考虑dp,设f[i]表示以i为根的子树中,i为端点的最长链的长度.dfs,用经过点u的最长链+次长链更新答案.并用当前链的长度更新最长链的长度.如果遇到环了,就要单独处理.在环上的答案肯定是环上两个点延伸出去的两条链+在环上经过的最短路.即f[x] + f[y] + pos[x] - pos[y].将环变成一段区间,那么转移中y的下标就有了限制,又因为是求最值,所以可以用单调队列优化.

          单调队列中维护的东西比较特殊,不仅有f[y],还涉及到pos[y],将f[y]-pos[y]看作一个整体来维护.因为距离的定义是最短路,所以在环上经过的路径一定是较短的那条路径.当i与队首的下标差>len/2时,说明已经不是最短路了,就要将队首提前.最后还要更新一下f[x].

          环上求最值可以用单调队列维护要记清楚了.当前点是否在树内或环内的判断标准也要熟记.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 400010;

int n,m,head[maxn],to[maxn],nextt[maxn],tot = 1;
int q[maxn],pos[maxn],cnt,a[maxn];
int dfs_clock,pre[maxn],low[maxn],fa[maxn],f[maxn],dist[maxn],ans;

void add(int x,int y)
{
    to[tot] = y;
    nextt[tot] = head[x];
    head[x] = tot++;
}

void solve(int x,int y)
{
    cnt = dist[y] - dist[x] + 1;
    for (int i = y; i != x; i = fa[i])
        a[cnt--] = f[i];
    a[cnt] = f[x];
    cnt = dist[y] - dist[x] + 1;
    for (int i = 1; i <= cnt; i++)
        a[i + cnt] = a[i];
    int l = 1,r = 1;
    pos[1] = 1;
    for (int i = 2; i <= cnt * 2; i++)
    {
        while (l <= r && i - pos[l] > cnt / 2)
            l++;
        ans = max(ans,a[pos[l]] + a[i] + i - pos[l]);
        while (l <= r && a[pos[r]] - pos[r] <= a[i] - i)
            r--;
        pos[++r] = i;
    }
    for (int i = 2; i <= cnt; i++)
        f[x] = max(f[x],a[i] + min(i - 1,cnt - i + 1));
}

void dfs(int u,int faa)
{
    pre[u] = low[u] = ++dfs_clock;
    for (int i = head[u];i;i = nextt[i])
    {
        int v = to[i];
        if (v == faa)
            continue;
        if (!pre[v])
        {
            fa[v] = u;
            dist[v] = dist[u] + 1;
            dfs(v,u);
            low[u] = min(low[u],low[v]);
        }
        else
            low[u] = min(low[u],pre[v]);
        if (pre[u] < low[v])
        {
            ans = max(ans,f[u] + f[v] + 1);
            f[u] = max(f[u],f[v] + 1);
        }
    }
    for (int i = head[u];i;i = nextt[i])
    {
        int v = to[i];
        if (v == faa)
            continue;
        if (fa[v] != u && pre[u] < pre[v]) //此时u是dfs最先进入环中的点 v在u访问其它儿子的时候就被访问到了,所以v是深度最大的点
            solve(u,v);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= m; i++)
    {
        int num,last;
        scanf("%d",&num);
        scanf("%d",&last);
        for (int i = 1; i < num; i++)
        {
            int t;
            scanf("%d",&t);
            add(last,t);
            add(t,last);
            last = t;
        }
    }
    dfs(1,0);
    printf("%d\n",ans);

    return 0;
}
posted @ 2018-01-16 20:45  zbtrs  阅读(219)  评论(0编辑  收藏  举报