BZOJ1023 [SHOI2008]仙人掌图

58 篇文章 0 订阅

Address

Solution

  • 第一次做仙人掌图。
  • 主要难在环中 DP 值的处理。
  • 注意题目中提到的性质:

    仙人图上的每条边,或者是这张仙人图的桥,或者在且仅在一个简单回路里,两者必居其一。

  • 所以当然先写个 Tarjan T a r j a n 啦 。

  • Tarjan T a r j a n 也是在 DFS D F S ,考虑 DP D P 的转移。
  • f[x] f [ x ] 表示以点 x x 为起点的最长链长度,ans 为答案。
  • dfn[x]<low[y](xy) d f n [ x ] < l o w [ y ] ( 存 在 边 x → y ) ,则 x,y x , y 不在一个边双内:
    ans=max{ans,f[x]+f[y]+1},f[x]=max{f[x],f[y]+1} a n s = max { a n s , f [ x ] + f [ y ] + 1 } , f [ x ] = max { f [ x ] , f [ y ] + 1 }
  • 对于每个环,我们找到环中深度最小的点记为 rt r t ,环中第 i i 个点为 xi,共有 q q 个点。
  • f[rt]=max{f[rt],f[xi]+min{i1,qi+1}}
  • 同时在环中任选两个点取最长链也能更新 ans a n s ,即 ans=max{f[xi]+i+f[xj]j}(1j<iq) a n s = m a x { f [ x i ] + i + f [ x j ] − j } ( 1 ≤ j < i ≤ q )
  • 那么对于每个 xi x i ,找到 f[xj]j f [ x j ] − j 最大的 j j 来更新显然最优,可以用单调队列优化成 O(n),因此总的时间复杂度 O(n+m) O ( n + m )

Code

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

using namespace std;

inline int get()
{
    char ch; int res = 0; bool flag = false;
    while (ch = getchar(), !isdigit(ch) && ch != '-');
    (ch == '-' ? flag = true : res = ch ^ 48);
    while (ch = getchar(), isdigit(ch))
        res = res * 10 + ch - 48;
    return flag ? -res : res;
}

const int N = 1e5 + 5, M = 1e7 + 5, L = 2e5 + 5;
int dfn[N], low[N], dep[N], fa[N], f[N], g[L], h[L];
int n, m, q, ans, tis;

struct Edge 
{
    int to; Edge *nxt;
}p[M], *lst[N], *P = p;

inline void Link(int x, int y)
{
    (++P)->nxt = lst[x]; lst[x] = P; P->to = y;
    (++P)->nxt = lst[y]; lst[y] = P; P->to = x;
}

inline void CkMin(int &x, int y) {if (x > y) x = y;}
inline void CkMax(int &x, int y) {if (x < y) x = y;}
inline int Min(int x, int y) {return x < y ? x : y;}

inline void solveDP(int rt, int x)
{
    q = dep[x] - dep[rt] + 1;
    for (int y = x; y != rt; y = fa[y], --q) 
        g[q] = f[y];
    g[q] = f[rt]; 
    q = dep[x] - dep[rt] + 1;
    for (int i = 1; i <= q; ++i)
        g[i + q] = g[i];

    int tmp = q >> 1, t = 1, w = 0;
    for (int i = 1, im = q << 1; i <= im; ++i)
    {
        while (t <= w && i - h[t] > tmp) ++t;
        if (t <= w) 
            CkMax(ans, g[i] + g[h[t]] + i - h[t]);
        while (t <= w && g[i] - i >= g[h[w]] - h[w]) --w;
        h[++w] = i; 
    }

    for (int i = 2; i <= q; ++i)
        CkMax(f[rt], g[i] + Min(i - 1, q - i + 1));
}

inline void Tarjan(int x)
{
    dfn[x] = low[x] = ++tis;
    for (Edge *e = lst[x]; e; e = e->nxt)
    {
        int y = e->to;
        if (y == fa[x]) continue;
        if (!dfn[y])
        {
            fa[y] = x;
            dep[y] = dep[x] + 1;
            Tarjan(y);
            CkMin(low[x], low[y]);
        }
        else CkMin(low[x], dfn[y]);

        if (dfn[x] < low[y])
        {
            CkMax(ans, f[x] + f[y] + 1);
            CkMax(f[x], f[y] + 1);
        }
    }

    for (Edge *e = lst[x]; e; e = e->nxt)
    {
        int y = e->to;
        if (fa[y] != x && dfn[x] < dfn[y])
            solveDP(x, y);
    }
}
int main()
{
    n = get(); m = get(); int k, x, y;
    while (m--)
    {
        k = get(); x = get();
        while (--k)
        {
            y = get();
            Link(x, y);
            x = y;
        }
    }
    Tarjan(1);
    printf("%d\n", ans);
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值