bzoj 1023: [SHOI2008]cactus仙人掌图 2125: 最短路 4728: 挪威的森林 静态仙人掌上路径长度的维护系列

%%% http://immortalco.blog.uoj.ac/blog/1955

一个通用的写法是建树,对每个环建一个新点,去掉环上的边,原先环上每个点到新点连边,边权为点到环根的最短/长路长度

1023 求仙人掌直径

树形dp,维护每个点向下的最长和次长路径长度,对原有的点直接更新答案,对新点可以把对应环上的点取出,倍长,破环成链,并用单调队列正反各扫一次

#include<cstdio>
char buf[5000000],*ptr=buf-1;
int _(){
    int x=0,c=*++ptr;
    while(c<48)c=*++ptr;
    while(c>47)x=x*10+c-48,c=*++ptr;
    return x;
}
const int N=500007;
int es[N],enx[N],ev[N],e0[N],e1[N],ep=2;
int dfn[N],low[N],tk=0,ss[N],sp=0,os[N],op,q[N],ql,qr;
int n,m,D,idp,ans=0,f1[N],f2[N];
void ae(int*e,int a,int b,int c){
    es[ep]=b;enx[ep]=e[a];ev[ep]=c;e[a]=ep++;
    es[ep]=a;enx[ep]=e[b];ev[ep]=c;e[b]=ep++;
}
int min(int a,int b){return a<b?a:b;}
void maxs(int&a,int b){if(a<b)a=b;}
void maxs(int&a,int&b,int c){
    if(a<=c)b=a,a=c;
    else if(b<c)b=c;
}
void tj(int w){
    dfn[w]=low[w]=++tk;
    for(int i=e0[w];i;i=enx[i]){
        int u=es[i];
        if(!u)continue;
        es[i^1]=0;
        if(!dfn[u]){
            ss[++sp]=u;
            tj(u);
            if(ss[sp]==u)--sp,ae(e1,w,u,1);
        }else if((low[w]=min(low[w],dfn[u]))==dfn[u]){
            op=0;
            while(sp&&dfn[ss[sp]]>dfn[u])os[++op]=ss[sp--];
            ae(e1,u,++idp,0);
            for(int j=1;j<=op;++j)ae(e1,idp,os[j],min(j,op+1-j));
        }
    }
}
void dfs(int w,int pa){
    for(int i=e1[w];i;i=enx[i]){
        int u=es[i];
        if(u==pa)continue;
        dfs(u,w);
        maxs(f1[w],f2[w],f1[u]+ev[i]);
    }
    if(w<=n)maxs(ans,f1[w]+f2[w]);
    else{
        op=0;
        for(int i=e1[w];i;i=enx[i]){
            int u=es[i];
            if(u!=pa)os[++op]=u;
        }
        os[++op]=pa;
        D=op>>1;
        ql=1,qr=0;
        for(int i=1;i<=D;++i)os[op+i]=os[i];
        op+=D;
        for(int i=1;i<=op;++i){
            while(ql<=qr&&q[ql]+D<i)++ql;
            if(ql<=qr)maxs(ans,f1[os[q[ql]]]+f1[os[i]]-q[ql]+i);
            while(ql<=qr&&f1[os[q[qr]]]-q[qr]<=f1[os[i]]-i)--qr;
            q[++qr]=i;
        }
        ql=1,qr=0;
        for(int i=op;i;--i){
            while(ql<=qr&&q[ql]-D>i)++ql;
            if(ql<=qr)maxs(ans,f1[os[q[ql]]]+f1[os[i]]+q[ql]-i);
            while(ql<=qr&&f1[os[q[qr]]]+q[qr]<=f1[os[i]]+i)--qr;
            q[++qr]=i;
        }
    }
}
int main(){
    fread(buf,1,sizeof(buf),stdin);
    idp=n=_();m=_();
    for(int i=0,c,a,b;i<m;++i){
        c=_();
        a=_();
        for(int j=1;j<c;++j){
            b=_();
            ae(e0,a,b,1);
            a=b;
        }
    }
    tj(1);
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

2125 多次询问仙人掌上两点间最短路

任意两点a,b间距离分情况考虑,设c=lca(a,b),若c是原有的点,则距离为树上a,b的距离dis(a,b),否则设x,y分别为a,b到c的路径上与c最近的点,则距离为dis(a,x)+dis(b,y)+环上x,y间的距离

倍增或链剖求一下lca再用前缀和特判一下环上情况

#include<cstdio>
#include<algorithm>
char buf[1000000],*ptr=buf-1;
int _(){
    int x=0,c=*++ptr;
    while(c<48)c=*++ptr;
    while(c>47)x=x*10+c-48,c=*++ptr;
    return x;
}
const int N=100007;
int n,m,q;
int es[N],enx[N],ev[N],e0[20007],e1[20007],ep=2,ss[20007],sp=0,idp,os[20007],op,d1[20007],d2[20007];
int dfn[20007],low[20007],tk=0;
void ae(int*e,int a,int b,int c){
    es[ep]=b;enx[ep]=e[a];ev[ep]=c;e[a]=ep++;
    es[ep]=a;enx[ep]=e[b];ev[ep]=c;e[b]=ep++;
}
int min(int a,int b){return a<b?a:b;}
void f0(int w){
    dfn[w]=low[w]=++tk;
    for(int i=e0[w];i;i=enx[i]){
        int u=es[i];
        if(!u)continue;
        if(!dfn[u]){
            ss[++sp]=i;
            es[i^1]=0;
            f0(u);
            low[w]=min(low[w],low[u]);
            if(ss[sp]==i)--sp,ae(e1,w,u,ev[i]);
        }else if((low[w]=min(low[w],dfn[u]))==dfn[u]){
            ++idp;op=0;
            ae(e1,idp,u,0);
            op=0;
            int s1=ev[i],s2=0;
            while(sp&&dfn[es[ss[sp]]]>dfn[u]){
                int e=ss[sp--];
                os[op++]=e;
                s2+=ev[e];
            }
            for(int p=0;p<op;++p){
                int e=os[p];
                ae(e1,idp,es[e],min(d1[es[e]]=s1,s2));
                d2[es[e]]=s1+s2;
                s1+=ev[e],s2-=ev[e];
            }
        }
    }
}
int fa[16][20007],dep[20007],Dep[20007];
void f1(int w,int pa){
    fa[0][w]=pa;
    for(int i=e1[w];i;i=enx[i]){
        int u=es[i];
        if(u==pa)continue;
        dep[u]=dep[w]+1;
        Dep[u]=Dep[w]+ev[i];
        f1(u,w);
    }
}
int main(){
    fread(buf,1,sizeof(buf),stdin);
    n=_();m=_();q=_();
    idp=n;
    for(int i=1,a,b,c;i<=m;++i){
        a=_();b=_();c=_();
        ae(e0,a,b,c);
    }
    f0(1);
    f1(1,0);
    for(int i=1;i<16;++i)for(int j=1;j<=idp;++j)fa[i][j]=fa[i-1][fa[i-1][j]];
    for(int i=0,a,b,ans;i<q;++i){
        a=_();b=_();
        if(dep[a]<dep[b])std::swap(a,b);
        ans=Dep[a]+Dep[b];
        for(int d=0,s=dep[a]-dep[b];d<16;++d)if(s>>d&1)a=fa[d][a];
        if(a==b)ans-=Dep[a]*2;
        else{
            for(int d=15;~d;--d)if(fa[d][a]!=fa[d][b])a=fa[d][a],b=fa[d][b];
            if(fa[0][a]<=n)ans-=Dep[fa[0][a]]*2;
            else{
                ans-=Dep[a]+Dep[b];
                int s=d1[a]-d1[b];
                if(s<0)s=-s;
                ans+=min(s,d2[a]-s);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

4728 带加点、加环操作维护仙人掌最长简单路径

对每条路径,路径上的点不会比两端更晚加入,所以可以离线处理

对新建的树点分治,分别考虑当前分治中心对应的子树(由于根改变,新边权要重新计算),若分治中心为环,则维护环上的前缀max/后缀max(由于环上两点间有两种路径,要分别维护),否则维护每个相邻点方向的最长路径

时间: 2024-10-12 13:55:18

bzoj 1023: [SHOI2008]cactus仙人掌图 2125: 最短路 4728: 挪威的森林 静态仙人掌上路径长度的维护系列的相关文章

bzoj 1023: [SHOI2008]cactus仙人掌图 tarjan索环&amp;&amp;环上单调队列

1023: [SHOI2008]cactus仙人掌图 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 1141  Solved: 435[Submit][Status] Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人图(cactus).所谓简单回路就是指在图上不重复经过任何一个顶点的回路. 举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路

bzoj 1023 [SHOI2008]cactus仙人掌图 ( poj 3567 Cactus Reloaded )——仙人掌直径模板

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1023 http://poj.org/problem?id=3567 因为lyd在讲课,所以有了lyd的模板.感觉人家写得好好呀!于是学习(抄)了一下.可以记一记. 反正各种优美.那个dp断环成链的地方那么流畅自然!tarjan里的那些 if 条件那么美! 不过十分不明白为什么边要开成4倍的.开成2倍的真的会RE.怎么分析仙人掌的边数? #include<iostream> #includ

BZOJ.1023.[SHOI2008]cactus仙人掌图(DP)

题目链接 类似求树的直径,可以用(类似)树形DP求每个点其子树(在仙人掌上就是诱导子图)最长链.次长链,用每个点子节点不同子树的 max{最长链}+max{次长链} 更新答案.(不需要存次长链,求解过程中先更新ans,然后再更新最长链即可) 设f[i]为点i的诱导子图中最长链的长度. 对于环,我们找一个环上dep[]最小的点x代表这个环 看做一个点(dep为按DFS顺序更新的),求出f[x],环以外的部分像树一样直接做就可以. 对于环的处理:f[x]比较显然,f[x]=max{f[v]+dis(

bzoj 1023: [SHOI2008]cactus仙人掌图

这个题真的不会啊 简而言之就是树形DP+环形Dp 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<queue> 7 #include<algorithm> 8 #include<vector> 9 #define M 50008 10 #def

【BZOJ】【1023】【SHOI2008】cactus仙人掌图

DP/仙人掌 题解:http://hzwer.com/4645.html->http://z55250825.blog.163.com/blog/static/150230809201412793151890/ QAQ了 呃……第一次做仙人掌的题目……感觉性质还是蛮神奇的(我是不是应该先做一点环套树的题目呢?>_>) 每个点都只会在一个简单环上,所以在dfs的时候,对于一个环,它上面的点是深度连续的一段(沿着father可以遍历这个环!),然后最后一个点再指回起始点,所以只要low改变了

【BZOJ 1023】 [SHOI2008]cactus仙人掌图

1023: [SHOI2008]cactus仙人掌图 Time Limit: 1 Sec  Memory Limit: 162 MB Submit: 1235  Solved: 482 [Submit][Status] Description 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人图(cactus).所谓简单回路就是指在图上不重复经过任何一个顶点的回路. 举例来说,上面的第一个例子是一张仙人图,而第二个不是--注意到它有三条简单

bzoj千题计划113:bzoj1023: [SHOI2008]cactus仙人掌图

http://www.lydsy.com/JudgeOnline/problem.php?id=1023 dp[x] 表示以x为端点的最长链 子节点与x不在同一个环上,那就是两条最长半链长度 子节点与x在同一个环上,环形DP,单调队列优化 对于每一个环,深度最小的那个点 有可能会更新 上层节点, 所以 每一个环DP完之后,更新 dp[深度最小的点] #include<cstdio> #include<iostream> #include<algorithm> using

[树形dp][Tarjan][单调队列] Bzoj 1023 cactus仙人掌图

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)同时出现在前两 个的简单回路里.另外,第三张图也不是仙人图,因为它并不是连通图

bzoj 1023 仙人掌图

Description 求一个仙人掌图的直径 Solution 仙人掌图有个性质,一条边要么是割边要么就是在环内,那么我们可以对它进行Dp辣! 令f[u]表示以u为根的子树最长链长度 如果u?v是桥的话转移就是ans=max(ans,f[u]+f[v]+1),f[u]=max(f[u],f[v]+1),因为当前f[u]都是由它的孩子更新来的 如果是环的话,变环为链,用单调队列dp出ans,然后用环上的f值更新f[u]的值就可以了,具体实现见代码 Code #include <bits/stdc+