洛谷 P4244 [SHOI2008]仙人掌图 II(圆方树+单调队列)

题目链接
题意

求仙人掌直径

思路

圆方树,树形dp,dp[u] 表示以u为根节点最长链
建圆方树,将点双判断仙人掌改改就好
原点到原点,正常dp
原点到方点,正常dp
方点到原点,圆点各点加上方点父亲节点应该是一个环,然后对这个环用基环树求直径(单调队列),但是注意复制序列时方节点父亲并没有被考虑,判断一下。

建树
在这里插入图片描述
加粗是方点,方点的儿子之间距离按遍历顺序考虑,而不是图上的简单路径权值和
在这里插入图片描述

坑点
在这里插入图片描述
考虑序列应该如图
在这里插入图片描述
a是实际存在但不在序列中的点

俩数据

hack 建圆方树后不考虑方节点的父亲节点,进行环形dp的
8
3
7 1 2 3 4 5 6 2
2 7 3
2 8 5
ac:4

8
3
7 1 2 3 4 5 6 2
2 7 3
2 8 6
ac:4

代码
#include <bits/stdc++.h>
using namespace std;

const int N = 50005;

int n, m;
vector<int> e[N];
int dfn[N], low[N], tot;
int sta[N], top;

vector<int> T[N<<1];
int ndc;

void tarjan(int u, int fa) {
    low[u] = dfn[u] = ++tot;
    sta[++top] = u;
    for(auto v : e[u]) if(v ^ fa) {
        if(!dfn[v]) {
            tarjan(v,u);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u]) T[u].push_back(v), --top;
            if(low[v] == dfn[u]) {
                T[u].push_back(++ndc);
                for(int x = -1; x^v;) T[ndc].push_back(x = sta[top--]);
            }
        }
        else low[u] = min(low[u], dfn[v]);
    }
}

int dp[N<<1], ans, q[N], a[N<<1];

void dfs(int u) {
    for(auto v : T[u]) dfs(v);
    if(u <= n) {
        for(auto v : T[u]) {
            ans = max(ans, dp[u]+dp[v]+1);
            dp[u] = max(dp[u], dp[v]+1);
        }
    }
    else {
        int l = 1, r = 0, cnt = 0;
        for(auto v : T[u]) a[++cnt] = v;
        for(int i = 1; i <= cnt; ++i) a[cnt+i] = a[i];
        for(int i = 1; i <= 2*cnt; ++i) {
// 两点之间的距离为这两点之间最短路径的距离,记得考虑消失的一个基环点即u父亲
// 这部分代码中三目运算都是考虑消失的方节点父亲
            while(l <= r && i-q[l]+(((i-1)/cnt != (q[l]-1)/cnt)?1:-1) > q[l]+cnt-i) ++l;
//            if(l <= r) ans = max(ans, dp[a[i]]+dp[a[q[l]]]+i-q[l]);
            if(l <= r) ans = max(ans, dp[a[i]]+dp[a[q[l]]]+i-q[l]+((i>cnt&&q[l]<=cnt)?1:0));
            while(l <= r && dp[a[i]]-i >= dp[a[q[r]]]-q[r]) --r;
            q[++r] = i;
        }
        for(int i = 1; i <= cnt; ++i) dp[u] = max(dp[u], dp[a[i]]+min(i-1,cnt-i));
    }
}

int main() {
    scanf("%d%d",&n,&m);
    ndc = n;
    for(int i = 1; i <= m; ++i) {
        int tmp, u, v;
        scanf("%d%d",&tmp,&u);
        while(--tmp) {
            scanf("%d",&v);
            e[u].push_back(v);
            e[v].push_back(u);
            u = v;
        }
    }
    tarjan(1,0);
    dfs(1);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值