• 【暖*墟】#动态规划# 基环树DP的学习与练习


    因为弃置了 四边形不等式优化 ,所以DP的任务还剩下 基环树DP / 插头DP / 动态DP

    当然,树形DP / 状压DP / 数位DP / 斜率优化DP 也还是要练习的......

    一 . 基环树的定义

    基环树:无向图,在一颗树的基础上,添加一条边。环上每个点都是树根。

    如果进行正常的DP,在环中是无法处理的。所以要把环拆开。

    假设要拆开的点是环上连在一起的u、v,这两个点存在限制关系(比如不同时选)。

    在拆开后,分别讨论 选择u不选择v选择v不选择u 两种情况即可。

    二 . “找环”操作

    在基环树上dfs,找到一个在此结点之前走过的相邻结点、就开始记录环。

    vector<int> G[MAXN]; //基环树
    
    int fa[MAXN]; //dfs时的父亲
    
    int dfn[MAXN], idx;  //访问的时间
    
    int loop[MAXN], cnt; //
    
    void get_loop(int u) {
        dfn[u] = ++ idx; //记录dfn序
        for (int i = 0; i < G[u].size(); i ++) {
            int v = G[u][i]; if(v == fa[u]) continue ;
            if(dfn[v]) { //找到了环
                if(dfn[v] < dfn[u]) continue ;
                loop[++ cnt] = v;
                for ( ; v != u; v = fa[v])
                    loop[++ cnt] = fa[v];
            } else fa[v] = u, get_loop(v); //继续递归
        }
    }

    三 . 有向的基环树

    基环内向树:每个点出度为1(因此每个环上点的子树,儿子指向父亲)。

    基环外向树:每个点入度为1(因此每个环上点的子树,父亲指向儿子)。

    • 将上图的所有边反转一下,就是基环外向树。

    四 . 基环树的直径

    基环树的直径定义为:基环树中最长的简单路径的长度。

    其中,简单路径指不重复经过任何点或边的路径。

    那么基环树的直径只可能有两种情况:

    1. 不经过环(在环上的某一点的子树中)
    2. 经过了环(某一段在环上)

    此图的直径为15+15(1)

    此图的直径为4+8+9(2)

    (1)【CF835F】 Roads in the Kingdom

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【CF835F】 Roads in the Kingdom
    求基环树删去环上任意一边后直径最小值。*/
    
    // 因为作者太菜所以请点击:https://www.cnblogs.com/penth/p/9668498.html void read(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=201000; const ll inf=0x3f3f3f3f3f3f3f3f; struct Edge{int ver,nxt,w;}a[N<<1]; int head[N],is[N],st[N],vis[N]; ll f[N],pre[N]; int gl[N],gr[N],hl[N],hr[N],hhl[N],hhr[N],ggl[N],ggr[N]; int n,m,tot,top; ll Ans; void get_cir(int x,int las){ if(vis[x]){ int p=top; while(st[p]^x)--p; for(int i=p;i<=top;i++) is[st[i-p+1]=st[i]]=1; st[0]=-1,top-=p-1; return; } vis[st[++top]=x]=1; for(int i=head[x];i&&~st[0];i=a[i].nxt) if(a[i].ver^las) get_cir(a[i].ver,x); if(~st[0]) vis[st[top--]]=0; } void dfs(int x,int las){ ll sec=0ll; for(int i=head[x];i;i=a[i].nxt) if(!is[a[i].ver]&&a[i].ver^las){ dfs(a[i].ver,x);f[a[i].ver]+=a[i].w; if(f[a[i].ver]>=f[x])sec=f[x],f[x]=f[a[i].ver]; else if(f[a[i].ver]>sec)sec=f[a[i].ver]; } Ans=max(Ans,f[x]+sec); } inline ll G(int x){return x?f[st[x]]-pre[x]:-inf;} inline ll H(int x){return x?f[st[x]]+pre[x]:-inf;} void work(){ st[0]=st[top]; for(int x=1;x<=top;++x){ for(int i=head[st[x-1]];i;i=a[i].nxt) if(a[i].ver==st[x]){ pre[x]=pre[x-1]+a[i].w; break; } dfs(st[x],0); } for(int i=1;i<=top;i++){ gl[i]=gl[i-1],ggl[i]=ggl[i-1]; hl[i]=hl[i-1],hhl[i]=hhl[i-1]; if(G(i)>G(gl[i])) ggl[i]=gl[i],gl[i]=i; else if(G(i)>G(ggl[i])) ggl[i]=i; if(H(i)>H(hl[i])) hhl[i]=hl[i],hl[i]=i; else if(H(i)>H(hhl[i])) hhl[i]=i; } for(int i=top;i;--i){ gr[i]=gr[i+1],ggr[i]=ggr[i+1]; hr[i]=hr[i+1],hhr[i]=hhr[i+1]; if(G(i)>G(gr[i])) ggr[i]=gr[i],gr[i]=i; else if(G(i)>G(ggr[i])) ggr[i]=i; if(H(i)>H(hr[i])) hhr[i]=hr[i],hr[i]=i; else if(H(i)>H(hhr[i])) hhr[i]=i; } ll ans; if(hl[top]!=gl[top]) ans=G(gl[top])+H(hl[top]); else ans=max(G(ggl[top])+H(hl[top]),G(gl[top])+H(hhl[top])); for(int i=1;i<top;i++){ ll val=pre[top]+H(hl[i])+G(gr[i+1]),tmp; if(gl[i]^hl[i]) tmp=G(gl[i])+H(hl[i]); else tmp=max(G(ggl[i])+H(hl[i]),G(gl[i])+H(hhl[i])); val=max(val,tmp); i++; if(gr[i]^hr[i]) tmp=G(gr[i])+H(hr[i]); else tmp=max(G(ggr[i])+H(hr[i]),G(gr[i])+H(hhr[i])); val=max(val,tmp); i--; ans=min(ans,val); } cout<<max(ans,Ans)<<endl; } void init(){ read(n); for(int i=1,u,v,w;i<=n;i++){ read(u),read(v),read(w); a[++tot].ver=v,a[tot].w=w,a[tot].nxt=head[u],head[u]=tot; a[++tot].ver=u,a[tot].w=w,a[tot].nxt=head[v],head[v]=tot; } get_cir(1,1); } int main(){init();work();return 0;}

    (2)【p4381】Island

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4381】Island
    有一个基环树和树组成的森林,共有n个结点。
    边有边权。试求其中所有联通块的直径之和。 */
    
    const int Maxn=1e6+7;
    
    int n,dfn[Maxn],id,headd[Maxn],tot,cnt;
    
    int vis[Maxn],que[2*Maxn],pd[Maxn],fa[Maxn],dvi[Maxn];
    
    long long las,nic,dep[2*Maxn],Q[2*Maxn],dis[Maxn],ans;
    
    struct Edge{ int ver,nextt,w; }e[4*Maxn];
    
    struct Hoop{ int ver,w; }edge[Maxn],ring[Maxn];
    
    int read(){ int s=0,w=1; char ch=getchar();
      while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
      while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w; }
    
    inline void add(int x,int y,int w)
     { e[++tot].ver=y,e[tot].nextt=headd[x],e[tot].w=w,headd[x]=tot; }
    
    inline long long K(int x){ return dep[x]-Q[x]; }
    
    inline void find_hoop(int x){ //找树上的环
        dvi[x]=1; while(1){ int y=edge[x].ver; //只有一条连边
            if(dvi[y]){ ring[++cnt].ver=y,ring[cnt].w=edge[y].w,dfn[y]=1;
                for(int i=x;i!=y;i=fa[i]) 
                    dfn[i]=1,ring[++cnt].ver=i,ring[cnt].w=edge[i].w; break;
            } dvi[y]=1,fa[y]=x,x=y; //继续往下寻找
        }
    }
    
    inline void dp(int x){ //树形dp
        vis[x]=1; dfn[x]=1;
        for(int i=headd[x];i;i=e[i].nextt){
            int y=e[i].ver;
            if(vis[y]||pd[y]) continue; dp(y);
            nic=max(nic,dis[x]+dis[y]+e[i].w);
            dis[x]=max(dis[x],dis[y]+e[i].w); 
        }
    }
    
    void dfs(int x,int fa,long long w){ //求环上每个节点的dep值
        if(w>=nic) nic=w;
        for(int i=headd[x];i;i=e[i].nextt){
            int u=e[i].ver;
            if(u!=fa&&!pd[u]) dfs(u,x,w+e[i].w);
        }
    }
    
    inline void first(int i){
        nic=0;dfs(ring[i].ver,0,0);dep[i]=nic; 
        nic=0;dp(ring[i].ver);las=max(las,nic);
    }
    
    inline void quee(){ //单调队列优化
        int head=1,tail=0;
        for(int i=1;i<=2*cnt;i++){
            if(i<=cnt) Q[i]=Q[i-1]+ring[i].w;
            else Q[i]=Q[i-1]+ring[i-cnt].w;
            if(head<=tail) las=max(las,K(que[head])+Q[i]+dep[i]);
            while(head<=tail&&K(que[tail])<=K(i)) --tail;
            que[++tail]=i;
            while(head<=tail&&que[head]<=i-cnt+1) ++head;
        } ans+=las;
    }
    
    int main(){
        n=read(); for(int i=1,y,w;i<=n;i++){
            y=read(),w=read(),add(i,y,w),add(y,i,w);
            edge[i].ver=y,edge[i].w=w;
        } for(int i=1;i<=n;i++){ if(dfn[i]) continue;
            cnt=0,id=0,las=0,find_hoop(i);
            for(int j=1;j<=cnt;j++) pd[ring[j].ver]=1;
            for(int j=1;j<=cnt;j++) first(j);
            for(int j=1;j<=cnt;j++) dep[j+cnt]=dep[j]; 
            quee();
        } printf("%lld
    ",ans); return 0;
    }

    五 . 相关例题详解

    (1)【P2607】[ZJOI2008] 骑士

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    #define N 1000010
    
    int fun[N],a,b; long long f[N][2];
    
    struct node{ int next,to,v; }e[2*N];
    
    int st[N],vis[N],n,s,tot,x1,x2,E;
    
    void add(int x,int y){e[tot].to=y,e[tot].next=st[x],st[x]=tot++;}
    
    void find_circle(int x,int pre){
        vis[x]=1; for (int i=st[x];~i;i=e[i].next){
            if ((i^1)==pre) continue;
            if (vis[e[i].to]){ x1=x,x2=e[i].to; E=i; continue; }
            find_circle(e[i].to,i);
        }
    }
    
    void dfs(int x,int pre){
        f[x][0]=0; f[x][1]=fun[x];
        for (int i=st[x];~i;i=e[i].next){
            if ((i^1)==pre) continue;
            if (i==E || (i^1)==E) continue;
            dfs(e[i].to,i);
            f[x][1]+=f[e[i].to][0];
            f[x][0]+=max(f[e[i].to][1],f[e[i].to][0]);
        }
    }
    
    int main(){
        memset(st,-1,sizeof st);
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            scanf("%d%d",&a,&b),add(i,b),add(b,i),fun[i]=a;
        long long ans=0;
        for (int i=1;i<=n;i++) {
            if (vis[i]) continue;
            find_circle(i,-2); dfs(x1,-1);
            long long temp=f[x1][0]; dfs(x2,-1);
            temp=max(temp,f[x2][0]); ans+=temp; 
        } printf("%lld
    ",ans);
    }

    (2)【p3533】rendezous

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3533】rendezous
    给定一棵内向森林,多次给定两个点a和b,求点对(x,y)满足:
    1.从a出发走x步和从b出发走y步会到达同一个点
    2.在1的基础上如果有多解,那么要求max(x,y)最小
    3.在1和2的基础上如果有多解,那么要求min(x,y)最小
    4.如果在1、2、3的基础上仍有多解,那么要求x>=y。 */
    
    //n个点n条边且每个点都有出边,是环套树森林。dfs把环套树拆成一堆树,用倍增求LCA。
    
    void reads(int &x){
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
        x=x*fx; //正负号
    }
    
    const int N=500019;
    
    int n,fa[N][20],root,q,circle[N],dep[N];
    int num[N],sum[N],tot,pos[N],vis[N];
    
    void findcircle(int x){
        int now=x;
        for(;;x=fa[x][0]){ //不停地找fa
            if(vis[x]==now) break;
            if(vis[x]) return;
            vis[x]=now;
        } tot++;
        while(!circle[x]){
            circle[x]=x; dep[x]=1;
            num[x]=++sum[tot];
            pos[x]=tot; x=fa[x][0];
        }
    }
    
    void dfs(int x){
        if(dep[x]) return;
        dfs(fa[x][0]);
        circle[x]=circle[fa[x][0]];
        dep[x]=dep[fa[x][0]]+1;
        for(int i=1;(1<<i)<dep[x];i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    }
    
    inline int lca(int x,int y){
        if(dep[x]<dep[y]) swap(x,y);
        int t=dep[x]-dep[y];
        for(int i=18;~i;i--)
            if(t&(1<<i)) x=fa[x][i];
        if(x==y) return x;
        for(int i=18;~i;i--)
            if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        return fa[x][0];
    }
    
    bool judge(int a,int b,int c,int d){
        if(max(a,b)<max(c,d)) return 1;
        if(max(a,b)>max(c,d)) return 0;
        if(min(a,b)<min(c,d)) return 1;
        if(min(a,b)>min(c,d)) return 0;
        if(a>=b) return 1; return 0;
    }
    
    int main(){
        reads(n),reads(q);
        for(int i=1;i<=n;i++) reads(fa[i][0]);
        for(int i=1;i<=n;i++) findcircle(i);
        for(int i=1;i<=n;i++)
            if(!circle[i]) dfs(i);
        while(q--){
            int x,y; reads(x),reads(y);
            if(pos[circle[x]]!=pos[circle[y]]){
                puts("-1 -1"); continue; //不是同一个环
            } if(circle[x]==circle[y]){
                printf("%d %d
    ",dep[x]-dep[lca(x,y)],dep[y]-dep[lca(x,y)]); continue;
            } int ans1=dep[x]-1,ans2=dep[y]-1,t=pos[circle[x]];
            x=num[circle[x]],y=num[circle[y]];
            int z1=(sum[t]+y-x)%sum[t],z2=sum[t]-z1;
            if(judge(ans1+z1,ans2,ans1,ans2+z2))
            printf("%d %d
    ",ans1+z1,ans2);
            else printf("%d %d
    ",ans1,ans2+z2);
        }
    }

     参考学习:https://blog.csdn.net/zzrh2018/article/details/81878766

                      https://www.cnblogs.com/mangoyang/p/9314823.html

                                ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    数据库练习
    pymysql
    数据库索引
    数据库查询
    数据库操作
    数据库建表
    数据库初识
    shell 编程
    Struts2与SpringMVC
    SpringAOP
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10419674.html
Copyright © 2020-2023  润新知