BZOJ 1023 [SHOI2008]cactus仙人掌图:圆方树+单调队列DP

9 篇文章 0 订阅
7 篇文章 0 订阅

题目传送门

题意:给出一个仙人掌图,边权都为1,求其直径。

仙人掌图:无向图的每条边至多存在于一个简单环中。

仙人掌图直径:Max(dis(u,v)) 1<= u < v <=n。dis(u,v)是u、v之间的最短路径。

题解:

先考虑退化情况:仙人掌退化为一棵树。很显然可以通过一个树形DP来解决,讨论路径的形态:1、分跨在一个点的两个子树中2、是一条从根节点到叶子节点的路径。每个点需要两个dp值记录子树中到叶子的最长和次长路径。然后进行简单的转移即可。
非退化情况:对仙人掌图求出圆方树。
对于圆点而言,处理方法和上边的一样。
对于方点而言,问题转化为:给出一个环基树,已知环上顺次都是那些点、以及每个点向外侧延伸的最大链长。求出环基树的直径,这个问题有一种极其经典的RMQ的有限队列解法,可以做到O(环长)的复杂度。因此总体复杂度是O(n)

圆方树详细资料:WC2017课件传送门

由于本人不喜欢破环操作。。。对环基树直径采用了分类讨论的方法:环上路径是否经过环的根。也可以做到O(环长)的复杂度。
我的tarjan求点双不需要处理长度=2的无效环问题。
这个题交了十几次才AC掉…已经不省人事了…然而这只是毒瘤入门题,看来想成为毒瘤是需要付出巨大的努力…………

Code:

#include<bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
const int maxn = 1e5+100;
vector<int>E1[maxn],ET[2*maxn],LenT[2*maxn];
int dfn[maxn],fa[2*maxn],len[maxn*2],dfs_clock;
bool inCircle[maxn];
int m,n,ans,N;
int dp[maxn][2];
int Q[maxn],head,tail,Max[maxn*2];;
inline void addEdge1(int x,int y){
    E1[x].pb(y);
}
inline void addEdgeT(int x,int y,int w){
    ET[x].pb(y);
    LenT[x].pb(w);
}
void input(){
    scanf("%d%d",&n,&m);
    N=n;
    for (int i=0;i<m;i++){
        int k,u;
        scanf("%d%d",&k,&u);
        for (int j=1;j<k;j++){
            int v;
            scanf("%d",&v);
            addEdge1(u,v);
            addEdge1(v,u);
            u = v;
        }
    }
}
void tarjan(int u){
    dfn[u] = ++ dfs_clock;
    for (int i=0;i<E1[u].size();i++){
        int v = E1[u][i];
        if (v==fa[u])continue;
        if (!dfn[v]){
            fa[v] =u;
            tarjan(v);
        }else if (dfn[v]<dfn[u]){
            n++;
            addEdgeT(v,n,0);
            int temp = u;
            len[n] = dfn[u]-dfn[v]+1;
            fa[n] = v;
            while (temp!=v){
                inCircle[temp] = true;
                addEdgeT(n,temp,min(dfn[temp]-dfn[v],len[n]-dfn[temp]+dfn[v]));
                temp = fa[temp];
            }
        }
    }
    if (!inCircle[u]){
        addEdgeT(fa[u],u,1);
    }
    dfs_clock--;
}
inline void update(int x,int w){
    if (w>=dp[x][0]){
        dp[x][1] = dp[x][0];
        dp[x][0]= w;
    }else if (w>dp[x][1]){
        dp[x][1] = w;
    }
}
void work(int squareU){
    head = 1;tail = 0;
    int length = len[squareU];
    for (int i=0;i<ET[squareU].size();i++){
        int v = ET[squareU][i];
        while (head<=tail&&head<i+1-length/2)head++;
        if (head<=tail&&head>=i+1-length/2){
            ans = max(ans,dp[v][0]+i+1+Q[head]);
        }
        while (head<=tail&&Q[tail]<dp[v][0]-i-1)tail--;
        Q[++tail] = dp[v][0]-(i+1);
    }
    for (int i=0;i<length/2;i++){
        Max[i+1] = max(Max[i],dp[ET[squareU][i]][0]+i+1);
    }
    for (int i=length/2;i<ET[squareU].size();i++){
        ans = max(ans,dp[ET[squareU][i]][0]+length-i-1+Max[length/2-(length-i-1)]);
    }
}
void dfs(int u){
    for (int i=0;i<ET[u].size();i++){
        dfs(ET[u][i]);
        update(u,dp[ET[u][i]][0]+LenT[u][i]);
    }
    if (u>N){
        work(u);
    }else{
        ans = max(ans,dp[u][0]+dp[u][1]);
    }
}
int main(){
    input();
    tarjan(1);
    dfs(1);
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值