啊,这可恶的圆方树

你离开我真会死。 提交于 2020-10-03 20:43:14

 



开新坑了——圆方树

圆方树


先来讲讲圆方树是个啥。
相对于tarjan对于有向有环图进行的缩点,圆方树在tarjan的基础上进行了进一步的拓展.
他能在无向图上进行,而对于有向图的强联通分量,则变为了无向图中的边双或点双。
而功能也是更加的强大,无向图->树。
但是也是对应能解决一部分的问题。
比如圆方树的板子题:求任意两点之间一定要经过的点的个数。
一定要经过的点,即为割点。
对于一个无向图,一个没有割点部分是点双(但是这并不严谨,点双的定义为,点双所涵盖的图中,任意两点之间有两条以上的简单路径相连(个人的理解))







点(边)双:若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图;(来自度娘)

其实对于上面的圆方树板子题,一定要经过的点就是割点,因为除了这个点,我们是不能再找到另一个路径前往下一个边双的,不然他就不是割点了(删除它以后,对图的连通性没有影响)。
其实圆方树原本是来解决仙人掌的问题的。因为对于仙人掌的每一个环,都是一个点双。。。

所以圆方树如何实现呢?

看这样的图

 

对于第一个无向图,可以发现它有4个点双。
在第二个图中,有4个方点,每一个对应了一个点双。
如图,我们把每一个点双中的点连接到在新图中对应的方点上。
这就变成了一个树。


一个方点有且只有一个点双对应。但是一个点可能连接很多个方点,那么就说明这是个割点。
那么怎么来求点双呢?
tarjan在无向图中的应用之一就是求割点。
其实也可以不用圆方树,可是圆方树好就好在建图极为的方便,和tarjan几乎是绝配,因为方点其实就是染色,所以在tarjan中就可以直接进行建树了,和有向图缩点的方向其实差不多


 


建树具体方案如下:
1. 根据tarjan算法,求出每个点双。
2. 拆掉每个点双内部的所有边。
3. 将这个点双对应的方点向这个点双内的每一个圆点连一条边。
这样每一个点双就成为了一个树的形态,这样所有的都连起来也就是一颗树了。




 

建树 代码

#include<bits/stdc++.h>
const int MN = 100005; int N, M, cnt; vector<int> G[MN], T[MN * 2]; //G是原图,T是圆方树 int dfn[MN], low[MN], dfc; int stk[MN], tp; void Tarjan(int u) { printf(" Enter : #%d\n", u); low[u] = dfn[u] = ++dfc; // low 初始化为当前节点 dfn stk[++tp] = u; // 加入栈中 for (int i=0;i<G[u].size();i++) { // 遍历 u 的相邻节点        int v=G[u][i]; if (!dfn[v]) { // 如果未访问过 Tarjan(v); // 递归 low[u] = std::min(low[u], low[v]); // 未访问的和 low 取 min if (low[v] >= dfn[u]) { // 标志着找到一个以 u 为根的点双连通分量 ++cnt; // 增加方点个数 printf(" Found a New BCC #%d.\n", cnt - N); // 将点双中除了 u 的点退栈,并在圆方树中连边 for (int x = 0; x != v; --tp) { //!!!! x = stk[tp]; T[cnt].push_back(x); T[x].push_back(cnt); printf("   BCC #%d has vertex #%d\n", cnt - N, x) } // 注意 u 自身也要连边(但不退栈) T[cnt].push_back(u); T[u].push_back(cnt); printf("   BCC #%d has vertex #%d\n", cnt - N, u); } } else low[u] = std::min(low[u], dfn[v]); // 已访问的和 dfn 取 min } printf(" Exit : #%d : low = %d\n", u, low[u]); printf(" Stack:\n   "); for (int i = 1; i <= tp; ++i) printf("%d, ", stk[i]); puts(""); } int main() { scanf("%d%d", &N, &M); cnt = N; // 点双 / 方点标号从 N 开始 for (int i = 1; i <= M; ++i) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); // 加双向边 G[v].push_back(u); } // 处理非连通图 for (int u = 1; u <= N; ++u) if (!dfn[u]) Tarjan(u), --tp; // 注意到退出 Tarjan 时栈中还有一个元素即根,将其退栈 return 0;
}


 这就是部分代码了。

对于我说的那个末班题嘛,你还需要一个lca,可供选择的有:倍增,树链,balalbala,反正我jio得还是倍增好写 虽然慢

码量巨大awa

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct yuanx
{
    ll next,to;
}tree[2000001];
struct edge
{
    ll next,to,v;
}e[2000001];
ll head[2000001],head2[2000001],tot2,tot,n,m,Q,dep[2000001];
ll f[2000001][23],fa[2000001],dfn[2000010],low[2000001],ccs;
ll dfsc,cnt,xx1[2000001],yy1[2000001];
bool vis[2000001],vis2[2000001];
inline ll read()
{
    char c=getchar();ll a=0,b=1;
    for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
    for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;
    return a*b;
}
void add(ll i,ll j)
{
    e[++tot].next=head[i];
    e[tot].to=j;
    head[i]=tot;
}
void add2(ll i,ll j)
{
    tree[++tot2].next=head2[i];
    tree[tot2].to=j;
    head2[i]=tot2;
}
void dfs(ll x,ll faa)
{
    dep[x]=dep[faa]+1;
    for(ll i=1;(1<<i)<=dep[x];i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(ll i=head2[x];i!=0;i=tree[i].next)
    {
        ll u=tree[i].to;
        if(u==faa)continue;
        f[u][0]=x;
        dfs(u,x);
    }
}
ll lca(ll x,ll y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(ll i=22;i>=0;i--)
    {
        if(dep[f[x][i]]>=dep[y])
        {
            x=f[x][i];
        }
        if(x==y)
        {
            return x;
            
        }
    }
    if(x==y)
    {
        return x;
    }
    for(ll i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];y=f[y][i];
        }
    }
    return f[x][0];
}
int s[2000001],top;
void tarjan(ll x)
{
    dfn[x]=low[x]=++dfsc;
    s[++top]=x;vis[x]=true;
    for(ll i=head[x];i!=0;i=e[i].next)
    {
        ll u=e[i].to;
        if(dfn[u]==0)
        {
            tarjan(u);
            low[x]=min(low[x],low[u]);
            if(low[u]>=dfn[x])
            {
                cnt++;
                for(int v=0;v!=u;top--)
                {
                    v=s[top];
                    add2(v,cnt);
                    add2(cnt,v);
                }
                add2(cnt,x);add2(x,cnt);
            }
        }
        else
        if(vis[u]==true)
        {
            low[x]=min(low[x],dfn[u]);
        }
    }
} 
ll lcaa(ll x,ll y)
{
    return (dep[x]+dep[y]-dep[lca(x,y)]*2-2)/2;
}
int main()
{ 
    n=read();m=read();
    cnt=n;
    for(ll i=1;i<=m;i++)
    {
        ll x=read();ll y=read();
        add(x,y);add(y,x);
        xx1[i]=x;yy1[i]=y;
    }
    for(ll i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            tarjan(i);
            top=0;
        }
    }
    dfs(1,1);
    Q=read();
    for(ll i=1;i<=Q;i++)
    {
        ll x=read();ll y=read();
        ll ans=0;
        ans=max(ans,lcaa(xx1[x],xx1[y]));
        ans=max(ans,lcaa(xx1[x],yy1[y]));
        ans=max(ans,lcaa(yy1[x],xx1[y]));
        ans=max(ans,lcaa(yy1[x],yy1[y]));
        cout<<ans<<endl;
    }
    return 0;
}

代码巨丑无比(逃

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!