欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-01-31 08:30:10
Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。 举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路:(4,3,2 ......
Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 3467  Solved: 1438
[][][]

Description

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

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

  举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路:(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。


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


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

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

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

 

Source

仙人掌DP

对于这种题的套路就是先考虑只是一棵树的情况,再特判环上的情况

如果是裸的树,我们用$f[i]$表示$i$号节点对应的最长链的长度,然后枚举任意两个点之间的边,进行更新

如果出现了环怎么办?

考虑如果是在环上,同样是用环上的两点去更新的答案,

任意两点对答案的贡献为$f[i]+f[j]+dis(i,j)$,$dis(i,j)$表示$i,j$两点间的最短路径

在环上,任意两点间存在两条路径,此时有两种处理方法:

1.正着来一遍再倒着来一遍

2.拆环成链

然后用单调队列维护一下就行了。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1e6 + 10, INF = 1e9 + 10;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
struct Edge {
    int u, v, nxt;
}E[MAXN << 1];
int head[MAXN], num = 1;
inline void AddEdge(int x, int y) {
    E[num] = (Edge){x, y, head[x]};
    head[x] = num++;
}
int N, M;
int fa[MAXN], dfn[MAXN], low[MAXN], Index, deep[MAXN];
int f[MAXN], ans = 0;
int a[MAXN << 1], Q[MAXN];
void DP(int x, int y) {
    int tot = 0;
    for(int i = y; i != x; i = fa[i]) a[++tot] = i; a[++tot] = x;
    reverse(a + 1, a + tot + 1);
    for(int i = 1; i <= tot; i++) a[i + tot] = a[i];
    int h = 1, t =0;
    for(int i = 1; i <= (tot << 1); i++) {
        while(h <= t && i - Q[h] > tot / 2) h++;
        if(h <= t) ans = max(ans, f[a[i]] + f[a[Q[h]]] + i - Q[h]);
        while(h <= t && f[a[Q[t]]] - Q[t] < f[a[i]] - i) t--;
        Q[++t] = i;
    }
    for(int i = y; i != x; i = fa[i])
        f[x] = max(f[x], f[i] + min(deep[i] - deep[x], deep[y] - deep[i] + 1));
}
void Tarjan(int x, int _fa) {
    fa[x] = _fa; dfn[x] = low[x] = ++Index; deep[x] = deep[_fa] + 1;
    for(int i = head[x], v; i != -1; i = E[i].nxt) {
        if((v = E[i].v) == _fa) continue;
        if(!dfn[v]) Tarjan(v, x), low[x] = min(low[x], low[v]);
        else low[x] = min(low[x], dfn[v]);
        if(dfn[x] < low[v]) ans = max(ans, f[x] + f[v] + 1), f[x] = max(f[x], f[v] + 1);
        //why is dfn?
        if(fa[v] != x && dfn[v] > dfn[x]) 
            DP(x, v);
    }
}
int main() {
#ifdef WIN32
    freopen("a.in", "r", stdin);
#else
#endif
    memset(head, -1, sizeof(head));
    N = read(); M = read();
    for(int i = 1; i <= M; i++) {
        int K = read(), pre = read();
        for(int j = 2; j <= K; j++) {
            int now = read();
            AddEdge(pre, now); AddEdge(now, pre), pre = now;
        }
    }
    Tarjan(1, 0);
    printf("%d\n", ans);
    return 0;
}