zoukankan      html  css  js  c++  java
  • 基环树初步

    
    

    预备知识

    说实话,基环树一般比较综合,所以一般只要就要具有图论基本知识便可以开始学习.

    警告

    博主实力不足,如果出错,请用力D他 .

    认识

    基环树,原体是树,有树上任意连一条边,就成了一个环,即基环树,一般特征,个点条边;

    由于有树的特征,所以经常会用一些树的算法来计算基环树;

    基础的知识

     

    找环

    寻找环,这是基环树的基础,也很简单.

    考虑记录每个点是否访问过,当再次经过这个点时,那么这个点就能找出这个环了.

    我们只要在dfs时记录点的前继即可.

    void get_cir(int x,int y,int f){
        int temp=x;stk[++tot]=x;id[x]=tot;w[y]=f;
        do{
            temp=pre[temp];
            stk[++tot]=temp;
            id[temp]=tot;
        }while(temp!=y);
        sum[1]=w[y];
        for(int i=2;i<=tot;i++)
            sum[i]=sum[i-1]+w[stk[i-1]];
    }//记录环 
    bool dfs1(int x,int fa){
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].next){
            int y=edge[i].to;
            if(y==fa) continue;
            if(!vis[y]) {
                pre[y]=x,w[y]=edge[i].w;
                if(dfs1(y,x)) return 1;
            }else {
                get_cir(x,y,edge[i].w);
                return 1;
            }
        }
        return 0;
    }//返回值为是否找到环 
    
    

    这个是根据原理自己构造的,需要注意的是,环处理出来的前缀和终点不是该点,即为从0点到点的距离,所以记录环中第2个for循环就是讲数组循环移动一次,当然这是我自己构造而导致的,不过也并不碍事;

    子树DP找直径以及最大深度

    这里应该是比较基础的,确实,两遍bfs或dfs理解很容易,但是相比之下,DP有更优的时间以及优秀的代码长度,所以DP找直径是必要的;

    ll f;
    ​
    void dfs2(int x,int fa){
        ll other=0;
        for(int i=head[x];i;i=edge[i].next){
            int y=edge[i].to;
            if(y==fa||id[y]) continue;
            dfs2(y,x);
            f=max(f,root[x]+root[y]+edge[i].w);
            f=max(f,other+root[y]+edge[i].w);//更新直径 
            other=max(other,root[y]+edge[i].w);
            dep[x]=max(dep[x],dep[y]+edge[i].w);//深度更新 
        }
        root[x]+=other;
    }//DP找直径以及最大深度

    进阶结合

    基环树,最终都是在环上处理问题,通常处理路径长度问题,因为博主实力不足,目前见过两种,都总结下来;

    set维护

    Roads in the Kingdom

    王国有座城市与条有长度的街道,保证所有城市直接或间接联通,我们定义王国的直径为所有点对最短距离中的最大值,现因财政危机需拆除一条道路并同时要求所有城市仍然联通,求所有拆除方案中王国直径的最小值

    显然,这是一道有基环树特征的题,考虑枚举拆除道路,即环上道路(不在环上就不连通了),那么如何快速求出直径是我们所需要的;

    我们可以先考虑直径不过环,那么可以先DP预处理出子树上直径,并同时处理出最大深度(后面要用).

    之后考虑环上直径,我们可以维护环上前缀和,注意前缀和必须以点结尾;那么,直径公式就很显然了;

     

    那么,我们可以用两个set维护,每次取出最大值,相加即可;

    还有一些细节问题需要注意:

    1. 显然,如果相等,我们可以取次小值最大的更新

    2. 万一不在一个环上怎么办,我们知道,环上有两种求直径,,那么我们如何保证一定是第一个公式呢.考虑我们在存前缀和时,是点与点之间的距离,那么我们可以先枚举这条边,那么,此时一定是第一个公式,这个可以想一想环上前缀和的特点;那么我们就可以在枚举点,枚举的边即为它与之间的边,每次到下一个点,使我们这个点的加上,即相当于变成了前缀和最后一个点,然后在set中加入,并删除以前的;

    3. 如何保证,考虑一下,如果,但是前缀和中,那么显然,而我们求出的是最优的,所以一定是后者,所以只要注意一下第一条所说的的情况,其他情况下.

    这样应该就能解决了,对了,multiset的结构体删除请注意一下,重载运算符可能会出差错;

    Code

    #include<bits/stdc++.h>
    #define maxn 400007
    #define ll long long
    #define mp(x,y) (d){x,y}
    #define st multiset<d>::iterator
    using namespace std;
    int n,head[maxn],pre[maxn],stk[maxn],id[maxn],tot;
    int cent,vis[maxn],w[maxn];
    ll sum[maxn],ans,dep[maxn],root[maxn];
    struct node{
        int next,to,w;
    }edge[maxn<<1];
    ​
    template<typename type_of_scan>
    inline void scan(type_of_scan &x){
        type_of_scan f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f;
    }
    template<typename top,typename... tops>
    inline void scan(top &x,tops&... X){
        scan(x),scan(X...);
    }
    ​
    inline void add(int u,int v,int w){
        edge[++cent]=(node){head[u],v,w};head[u]=cent;
        edge[++cent]=(node){head[v],u,w};head[v]=cent;
    }
    ​
    void get_cir(int x,int y,int f){
        int temp=x;stk[++tot]=x;id[x]=tot;w[y]=f;
        do{
            temp=pre[temp];
            stk[++tot]=temp;
            id[temp]=tot;
        }while(temp!=y);
        sum[1]=w[y];
        for(int i=2;i<=tot;i++)
            sum[i]=sum[i-1]+w[stk[i-1]];
    }//记录环 
    bool dfs1(int x,int fa){
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].next){
            int y=edge[i].to;
            if(y==fa) continue;
            if(!vis[y]) {
                pre[y]=x,w[y]=edge[i].w;
                if(dfs1(y,x)) return 1;
            }else {
                get_cir(x,y,edge[i].w);
                return 1;
            }
        }
        return 0;
    }//返回值为是否找到环 
    ​
    ll f;
    ​
    void dfs2(int x,int fa){
        ll other=0;
        for(int i=head[x];i;i=edge[i].next){
            int y=edge[i].to;
            if(y==fa||id[y]) continue;
            dfs2(y,x);
            f=max(f,root[x]+root[y]+edge[i].w);
            f=max(f,other+root[y]+edge[i].w);//更新直径 
            other=max(other,root[y]+edge[i].w);
            dep[x]=max(dep[x],dep[y]+edge[i].w);//深度更新 
        }
        root[x]+=other;
    }//DP找直径以及最大深度 
    struct d{
        ll val,pos;
        bool operator <(const d &x) const{
            return val>x.val;
        }
    };
    ​
    multiset<d>a,b;
    ​
    int main(){
        scan(n);ans=2305843009213693652;
        for(int i=1,u,v,w;i<=n;i++) scan(u,v,w),add(u,v,w);
        dfs1(1,0);
        for(int i=1;i<=tot;i++) dfs2(stk[i],0);
        for(int i=1;i<=tot;i++)
            a.insert(mp(sum[i]+dep[stk[i]],i)),b.insert(mp(-sum[i]+dep[stk[i]],i));
        for(int i=1;i<=tot;i++){
            st x=a.begin(),y=b.begin();
            ll ol=0;
            if(x->pos==y->pos){
                st dir=a.begin();dir++;
                ol=dir->val+y->val;
                dir=b.begin();dir++;
                ol=max(ol,dir->val+x->val);
            }else ol=x->val+y->val;
            ans=min(ans,ol);
            x=a.find(mp(sum[i]+dep[stk[i]],i)),y=b.find(mp(-sum[i]+dep[stk[i]],i));
            a.erase(x),b.erase(y);
            a.insert(mp(sum[i]+sum[tot]+dep[stk[i]],i)),b.insert(mp(-sum[i]-sum[tot]+dep[stk[i]],i));
        }
        printf("%I64d
    ",max(f,ans));
    }
    
    

    单调队列优化

    P4381 [IOI2008]Island

    你准备浏览一个公园,该公园由 个岛屿组成,当地管理部门从每个岛屿 出发向另外一个岛屿建了一座长度为的桥,不过桥是可以双向行走的。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。相对于乘船而言,你更喜欢步行。你希望经过的桥的总长度尽可能长,但受到以下的限制:

    • 可以自行挑选一个岛开始游览。

    • 任何一个岛都不能游览一次以上。

    • 无论任何时间,你都可以由当前所在的岛 去另一个从未到过的岛 。从

    有如下方法:

    • 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离中。

    • 渡船:你可以选择这种方法,仅当没有任何桥和以前使用过的渡船的组合可以由 走到 (当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。

    注意,你不必游览所有的岛,也可能无法走完所有的桥。

    我们很容易分析出这是一个基环树森林,分别计算每一个基环树上的直径,但是我们不能排除环的干扰,难道要暴力枚举环上边,博主并没有尝试,决定口胡;

    与set的连接

    以下为博主口胡部分(并不是主流的正解做法):

    根据公式;

    我们可以用上面的方法枚举断边,然后用set维护,求出最大直径,之后对于每一个基环树进行操作,总时间复杂度

    ,估计可以通过此题,但是因为这道题时间太过久远,博主在之前写过单调队列,已经不想再写一遍了,不过这种方法预测可行(还挺具有普适性的);

    主流的单调队列

    显然,我们可以枚举点,不过这样会T,但是考虑当时,我们可以用单调队列维护的最大值,然后断环成链,出队条件即为,那么就可以很快地操作了.时间复杂度.

    这里不再附上代码,因为当时代码太丑,有很大一部分是与他人代码雷同(因为不会写),为了防止误导,就不再附上代码;

     

    ps:当遇到这类题时会再次update

  • 相关阅读:
    JavaScript入门知识点整理
    正则表达式
    bootstrap css编码规范
    JavaScript高级编程(学习笔记)
    【 D3.js 选择集与数据详解 — 2 】 使用data()绑定数据
    bootstrap table:JQuery中each方法绑定blur事件监听input输入是否合法,进入死循环
    bootstrap-table中导出excel插件bootstrap-table-export使用
    托业考后感
    《Pride and Prejudice》英文版读后记忆
    迷茫的当下,我在做什么
  • 原文地址:https://www.cnblogs.com/waterflower/p/11741989.html
Copyright © 2011-2022 走看看