仙人掌&圆方树学习笔记

定义

一个有向联通图中,任意的一条边最多出现在一个环中。
1.jpg

dfs树

dfs树,之前没有听过的名词,可能是我太弱了。但是这个名词既然被课件提到了,那么还是有必要去复习一下的。所谓\(dfs\)树,也就是深度优先搜索树,是一种将图转变成树的方法,还有两种方法分别是\(bfs\)树和圆方树(这个玩意比较难理解)。
熟悉的tarjan(这个万能的东西),其实就是根据dfs序来构建一个树,我们熟悉的缩点,强连通分量都是一个道理。
在OI中,有一些图的问题,如果能够将图转化成树,可能有一些很方便的解法。(来自课件)

bfs树

说老实话,其实我现在还不知道自己有做过什么题目是用过bfs树的。
仿照dfs树的定义,bfs树就是基于bfs顺序建立起来的树。
(我不懂,我就不多说了)

圆方树

学的我整个人都要不对了(营员交流课件看的我云里雾里)。
讲的明白一点(我只会这样的东西),处理仙人掌的问题,可以用圆方树来做。
圆方树:我们将所有的点分成两部分,一部分叫做圆点(原来就有的点),和方点。方点就是给每一个点双新建的节点。
偷来的
这样定义圆方树就有一个性质,和每一个圆点相连的点(方点),一定是方点(圆点)。
这就可能让人想到\(tarjan\)算法时的染色。
那么我们在解决仙人掌问题时,我们就定义原来仙人掌上的点是圆点,然后给每一个环都建立一个方点
为什么要建立这个圆点和方点?其实一开始我也不知道为什么要这么做。谈一下自己的理解:在一个仙人掌上,我们用方点来记录原来点双的信息,圆点来记录原来的节点的信息,这个和tarjan缩点还是非常相似的,然后我们就把这个仙人掌的“大叶子”成功变成了一个点,那么就可以建成一棵树了。(这样的想法非常的巧妙)
接下来的3个玩意如果有时间我一定会全部补起来。

圆方树的优美特性

  • 无论怎么换根,圆方树的形状一定是不会变的,这个是因为你把一个图连成了若干个菊花。(显然我们得到的圆方树是一个无根树)
  • 圆方树的子树=远仙人掌的子树。
  • 方点不会和方点连在一起。显然

点双

点双这个东西我之前的博客里没有提到过,之后来补坑(如果沃记得住):点双,全名叫做点双连通分量。
在一个联通图中如果没有割点,那么这个连通图就是一个点双连通图。
变向的说:也就是在一个图中,如果任意两点之间都有两条路径
所谓极大就是如果一个联通子图\(G1\)是点双,那么不存在又一个原图的点双联通子图满足\(G1 \in G2\)
同样的道理,点双连通分量的定义就是在一个非点双连通图的极大的点双联通子图。

边双

有了点双,那么也有边双的存在,所谓边双,全名叫边双连通分量。
在一个联通图中如果没有割边,那么这个连通图就是一个边双连通图。
同理,边双联通分量就是在一个非边双连通图中的极大的边双联通子图。
007rAy9hly1g0u807dzwbj30f509waa1.jpg
以上的两张图中标红的就是割点和割边。

关于边双点双的注意事项

关于点双和边上他们都死了,都是针对于无向图而言的,我们最熟悉的强连通分量\(tarjan\)是在有向图中的,而点双边双实在无向图中的。

仙人掌转圆方树的做法

首先我们将所有仙人掌上原有的点设为圆点,然后对于每一个环都建立一个方点,每一个环的方点和环上的圆点都连一个边,然后将原来的环全部都删去。这样就形成了一棵树。
007rAy9hgy1g0u8np7kogj30vg0ar0v7.jpg
偷图\(\times 2\)。(不要喷我QwQ)

仙人掌&圆方树的应用

仙人掌上两点最短路

比如说是bzoj2125[最短路],像这种题目。
我们建完树之后,考虑点u-v的最短路径,显然就是:在圆方树上u-v的路径上的所有边权之和,加上每个环(方点)中连出去的两个点的最短距离。
现在问题就是:如何求出环上两个点的最短路径。考虑这样设定边权,首先显然圆圆边的边权就是原图的边权,然后设一个环在搜索树中深度最小的点为这个环的根,则方圆边的边权是环的根到这个点的最短距离,这个可以在Tarjan的时候直接求出。
第一次写,参考了别人的代码

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf 0x3f3f3f3f
# define pb push_back
# define dd double
# define fi first
# define se second
# define pii pair<int,int>
# define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
inline void write(int x){
    if (x<0){putchar('-');write(-x);return;}
    if (x>9) write(x/10); putchar(x%10+'0'); 
}
# define N 20005
# define M 200005
int dfn[N],low[N],dist[N],lst[N],f[N][20],sum[N][20],len[N],type[N],S[N],dep[N];
int tot,top,tin,n,m,q;
struct graph{
    int cnt,H[N];
    graph(){cnt=0;}
    struct edge{
        int to,nt,w;
    }E[M<<1];
    void addedge(int u,int v,int w){
        E[++cnt]=(edge){v,H[u],w}; H[u]=cnt;
    }
}G1,G2;
void solve(int u,int v){
    int t; len[++tot]=dist[S[top]]-dist[u]+lst[S[top]];
    do{
        t=S[top--];
        int A=dist[t]-dist[u],B=len[tot]-A;
        G2.addedge(tot,t,min(A,B));
        type[t]=(A<=B);
    } while (t!=v);
    G2.addedge(u,tot,0);
}
void dfs(int u,int fa){
    for (int e=G2.H[u];e;e=G2.E[e].nt){
        int v=G2.E[e].to;
        f[v][0]=u; dep[v]=dep[u]+1; sum[v][0]=G2.E[e].w;
        dfs(v,u);
    }
}
void tarjan(int u,int fa){
    dfn[u]=low[u]=++tin; S[++top]=u;
    for (int e=G1.H[u];e;e=G1.E[e].nt){
        int v=G1.E[e].to; if (v==fa) continue;
        if (!dfn[v]) {
            dist[v]=dist[u]+G1.E[e].w;
            tarjan(v,u);
            if (low[v]>dfn[u]) top--,G2.addedge(u,v,G1.E[e].w);
            else if (low[v]==dfn[u]) solve(u,v);
            low[u]=min(low[u],low[v]);
        }
        else low[u]=min(low[u],dfn[v]),lst[u]=G1.E[e].w;
    }
}
int lca(int u,int v){
    if (dep[u]<dep[v]) swap(u,v);
    int t=dep[u]-dep[v],res=0;
    for (int i=15;~i;i--) if (t&(1<<i)) res+=sum[u][i],u=f[u][i];
    if (u==v) return res;
    for (int i=15;~i;i--) if (f[u][i]!=f[v][i]) res+=sum[u][i]+sum[v][i],u=f[u][i],v=f[v][i];
    if (f[u][0]<=n) return sum[u][0]+sum[v][0]+res;
    int A=sum[u][0],B=sum[v][0],minn;
    if (type[u]==type[v]) minn=min(abs(A-B),len[f[u][0]]-abs(A-B));
    else minn=min(A+B,len[f[u][0]]-A-B);
    return res+minn;
}
int main(){
    n=gi(),m=gi(),q=gi();tot=n;
    for (int i=1;i<=m;i++){
        int u=gi(),v=gi(),w=gi();
        G1.addedge(u,v,w); G1.addedge(v,u,w);
    }
    tarjan(1,0); dfs(1,0);
    for (int j=1;j<=15;j++) 
        for (int i=1;i<=tot;i++) 
            f[i][j]=f[f[i][j-1]][j-1],sum[i][j]=sum[i][j-1]+sum[f[i][j-1]][j-1];
    while (q--){
        int u=gi(),v=gi();
        printf("%d\n",lca(u,v));
    }
    return 0;
}

最大独立集(仙人掌DP)

小C的独立集
传送门
题意:在一个仙人掌中,取若干个不相连的点,并使取出的点的个数最多。

分析

也分析不出什么东西我们参考tarjan构造dfs树的过程,在tarjan中用dp解决问题。
\(f[i][j][k]\)表示考虑i的子树,i点选择情况为j,i到父亲对应的环边底部的点选择情况为k时的最大独立集。
从底部开始DP,在环的入点和出点进行特殊的转移就可以了。

代码
# include <bits/stdc++.h>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf 0x3f3f3f3f
# define pb push_back
# define dd double
# define fi first
# define se second
# define pii pair<int,int>
# define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
# define N 100005
struct graph{
    int cnt,H[N];
    struct edge{
        int to,nt;
    }E[N<<1];
    graph(){cnt=0;}
    void addedge(int u,int v){
        E[++cnt]=(edge){v,H[u]}; H[u]=cnt;
    }
}G;
int dfn[N],fa[N],f[N][2][2],h[2][2];
bool top[N],bot[N];
int tin,n,m,ans=0;
void tarjan(int u,int ft){
    dfn[u]=++tin; fa[u]=ft;
    for (int e=G.H[u];e;e=G.E[e].nt){
        int v=G.E[e].to; if (dfn[v]&&v!=ft) bot[u]=1;
    }
    f[u][1][bot[u]]=1;
    for (int e=G.H[u];e;e=G.E[e].nt){
        int v=G.E[e].to; 
        if (v==ft) continue;
        if (!dfn[v]) {
            tarjan(v,u);
            ms(h,0);
            for (int j=0;j<2;j++) 
                for (int k=0;k<2;k++) 
                    for (int x=0;x<2;x++)
                        for (int y=0;y<2;y++){
                            if (j&&x) continue;
                            if (top[v]&&j&&y) continue;
                            h[j][k|(y&!top[v])]=max(h[j][k|(y&!top[v])],f[u][j][k]+f[v][x][y]);
                        }
            for (int j=0;j<2;j++) 
                for (int k=0;k<2;k++) 
                    f[u][j][k]=h[j][k];
        } 
        else if (dfn[v]<dfn[u]){
            int node=u;
            while (fa[node]!=v) node=fa[node];
            top[node]=1;
        }
    }
}
int main(){
    n=gi(),m=gi();
    for (int i=1;i<=m;i++){
        int u=gi(),v=gi();
        G.addedge(u,v);  G.addedge(v,u);
    }
    for (int i=1;i<=n;i++) {
        if (dfn[i]) continue;
        tarjan(i,-1);
        int tmp=0;
        for (int j=0;j<=1;j++) 
            for (int k=0;k<=1;k++) tmp=max(f[i][j][k],tmp);
        ans+=tmp;
    }
    printf("%d\n",ans);
    return 0;
}

仙人掌上求最长链

其实用DP或者是topo排序感觉都可以,我没有做过,做了之后再补坑。

点分治

树上的所有的题目都可以放到仙人掌上,比如说是在仙人掌上长度小于等于\(n\)的路径条数。
如果没有仙人掌的限制就是一个非常简单的点分治,但是加上了仙人掌的限制对于我这种蒟蒻来说就是一个难题。
但是难度就是方点的信息的传递和点分的中心的寻找。详细请见:http://immortalco.blog.uoj.ac/blog/1955

对于圆方树和仙人掌的总结

沃一开始认为还是非常难得,但是感觉还是还好,就是前置的只是点双分量,还有dfs序建树要比较熟练。
圆方树就是圆点方点的各个信息之间的计算,其实很多要用圆方树的代码都可以用树形DP乱搞,在遇到环的时候根据特殊性质来特殊转移。

广义圆方树

刚刚想要休息一会,就发现了圆方树原来还有广义笑哭好学的沃还是看了一下。
一句话概括掉:放在有向图中的圆方树。
其他我也不知道了。

转载于:https://www.cnblogs.com/chhokmah/p/10528713.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值