• 点、边双,圆方树


    集训第一天,好累,果然停OI一个月还是不习惯

    讨论圆方树之前,我们先来考虑如下定义:

    点双:无向图的极大子图,使得该子图内无割点

    边双,无向图的极大子图,使得该子图内无割边

    易发现,一条边至多属于一个边双,一个点却可能不只属于一个点双

    求割边:考虑到当边(u,v)满足dfn[u] < dfn[v]且low[v] > dfn[u],此时割掉(u,v),v及其子树为一个边双

    易得将所有割边删去,原图即为若干边双

    求割点类似,不过多讨论一种为根的情况即可

    当发现 low[v] >= dfn[u]时,把v及v以上的点从栈中弹出,再加上u, 共同形成⼀个点双。
    void tarjan(int u)
    {
        dfn[u] = low[u] = ++idx;
        st[++top] = u;
        for(int i = fir[u];i;i = e[i].next)
        {
            int v = e[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                low[u] = min(low[u],low[v]);
                if(low[v] >= dfn[u])
                {
                    ++cnt,a[n + cnt] = 1;    
                    tradde(u,n + cnt);
                    int t = 0;
                    while(t != v)
                    {
                        t = st[top--];
                        tradde(t,n + cnt);
                        a[n + cnt]++;
                    }
                }
            }
            else low[u] = min(low[u],dfn[v]);
        }
    }
    View Code

    圆方树

    原图中的每个点为圆点。

    现在将点双内部的边全部拆去,建立一个方点存储该点双内需要的信息,并将该方点与各个圆点相连

    这样处理后,整个图就变成一颗树,且易发现,只有圆点和方点之间有边

    因此就可以利用这些性质,做一些看似没有思路的题

    CF487E :tourists : https://www.luogu.org/problemnew/show/CF487E

    给出⼀张图,点有点权。每次询问两点之间的简单路径中,权值的
    最⼩值最⼩是多少。带修。n, m, q ≤ 1000000
     
    考虑建圆方树,每个方点存储该点双中权值最小值,树剖即可
     
    [APIO2018]铁人两项
    给出⼀个(不⼀定连通)的图,求有多少个三元组 (s, c, f) 满⾜ s, c, f 都是图中的点,且存在⼀条从s到c的路径和⼀条从c到f的路径,使得两条路径没有公共点(除c外)。三元组有序
     
    考虑O(n^2)暴力,建圆方树,枚举圆点点对,这时计算他们路径上有多少个点即可
    考虑到圆点会被计算两次,因此将方点的权值设为点双大小,圆点的权值设为-1,计算路径长度即可
     
    考虑优化,我们可以尝试计算每个点在路径中被包含了多少次,即两种:
    1.作为中转点:枚举每个子树v,则对答案贡献为sz[v] * (sum - (u <= n) - sz[v]) * a[u],这里a[u]为u的权值
    2.作为起点或终点,此时该点必须为圆点,答案为(总节点数-1) * 2(起终点各为一遍)
    另外再考虑u的子树以外的点的路径,则对答案贡献(sz[u]  - (u <= n)) * (sum - sz[u])
    注意:sz只计算圆点数,因此要特别考虑(可以综合上面式子理解)
    #define O(x) cout << #x << " " << x << endl; 
    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    using namespace std;
    typedef long long ll;
    inline int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            (ans *= 10) += ch - '0';
             ch = getchar();
        }
        return ans * op;
    }
    const int maxn = 4e5 + 5;
    
    struct egde
    {
        int to,next;
    }e[maxn],tr[maxn];
    int head[maxn],tot;
    void tradde(int u,int v)
    {
        tr[++tot].next = head[u];
        head[u] = tot;
        tr[tot].to = v;
        swap(u,v);
        tr[++tot].next = head[u];
        head[u] = tot;
        tr[tot].to = v;    
    }
    int fir[maxn],alloc;    
    void adde(int u,int v)
    {
        e[++alloc].next = fir[u];
        fir[u] = alloc;
        e[alloc].to = v;
        swap(u,v);
        e[++alloc].next = fir[u];
        fir[u] = alloc;
        e[alloc].to = v;    
    }
    int n,m;
    int low[maxn],st[maxn],dfn[maxn],a[maxn],idx,top,cnt;
    int sum;
    ll ans;
    int sz[maxn];
    void tarjan(int u)
    {
        dfn[u] = low[u] = ++idx;
        st[++top] = u;
        for(int i = fir[u];i;i = e[i].next)
        {
            int v = e[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                low[u] = min(low[u],low[v]);
                if(low[v] >= dfn[u])
                {
                    ++cnt,a[n + cnt] = 1;    
                    tradde(u,n + cnt);
                    int t = 0;
                    while(t != v)
                    {
                        t = st[top--];
                        tradde(t,n + cnt);
                        a[n + cnt]++;
                    }
                }
            }
            else low[u] = min(low[u],dfn[v]);
        }
    }
    void dfs1(int u,int fa)
    {
        if(u <= n) sz[u] = 1;
        for(int i = head[u];i;i = tr[i].next)
        {
            int v = tr[i].to;
            if(v == fa) continue;
            dfs1(v,u);
            sz[u] += sz[v];
        }
    }        
    void dfs(int u,int fa)
    {
        ans = ans + 1ll * a[u] * (sz[u] - (a[u] == -1)) * (sum - sz[u]);
        for(int i = head[u];i;i = tr[i].next)
        {
            int v = tr[i].to;
            if(v == fa) continue;
            dfs(v,u);
            ans += 1ll * a[u] * sz[v] * (sum - (a[u] == -1) - sz[v]);
        }
        if(a[u] == -1) ans += 2ll * (sum - 1) * a[u];
    }
    int main()
    {
        memset(a,-1,sizeof(a));
        n = read(),m = read();
        for(int i = 1;i <= m;i++) 
        {
            int u = read(),v = read();
            adde(u,v);
        }
        for(int i = 1;i <= n;i++) 
        {
            if(!dfn[i]) 
            {
                sum = 0;
                tarjan(i);
                dfs1(i,0);
                sum = sz[i];
                dfs(i,0);
            }
        }
        printf("%lld",ans);
    }
        
        
    
        
    View Code

    总结:

    据说圆方树还可以解决仙人掌问题,但是菜鸡也不知道啥是仙人掌...

    感觉圆方树还有很多奇妙性质没有理解透彻,以后再巩固

  • 相关阅读:
    大文件处理
    查看系统声卡信息
    C# 禁止程序多个实例运行
    C#绘制传感器代码
    Arcgis 属性表模糊查询
    Python筛选Excel列数据,并导出!
    c#实现:返回n到m之间的所有素数
    C# 判断一个整数是否是素数!使用bool IsPrim(int n)实现!
    打开Arcgis,ArcToolbox却打不开,还闪退!!!
    AE常用功能
  • 原文地址:https://www.cnblogs.com/LM-LBG/p/11219097.html
Copyright © 2020-2023  润新知