点、边双,圆方树

集训第一天,好累,果然停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

 

总结:

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

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

posted on 2019-07-20 20:19  L_M_A  阅读(231)  评论(0编辑  收藏  举报

导航