zoukankan      html  css  js  c++  java
  • D8 双连通分量

    记得有个梗那一天,zw学生zzh大佬说逃不掉的路变成a不掉的题哈哈哈哈;

    分离的路径:

    BZOJ 1718
    POJ 3177
    LUOGU 286

    思路:在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。缩点后,新图是一棵树,树的边就是原无向图的桥。

    现在问题转化为:在树中至少添加多少条边能使图变为双连通图
    结论:添加边数=(树中度为1的节点数+1)/2;

    具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。
    然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

    点评:
    边双连通分量缩点后,原图变成一颗真正的树,而树上各种操作可以和其他知识点结合起来。
    这种敏感性要有,比如缩点之后就可以快速求必经边,必经点之类的。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=3e5+10;
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1,ch=getchar();
        while (!isdigit(ch)) ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    int ver[maxn<<1],Next[maxn<<1],head[maxn],len=1;
    inline void add(int x,int y)
    {
        ver[++len]=y,Next[len]=head[x],head[x]=len;
    }
    int dfn[maxn],low[maxn],id;
    bool bridge[maxn<<1];
    inline void tarjan(int x,int inedge)
    {
        dfn[x]=low[x]=++id;
        for (int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if (!dfn[y])
            {
                tarjan(y,i);
                low[x]=min(low[x],low[y]);
                if (low[y]>dfn[x])
                    bridge[i]=bridge[i^1]=1;
            }
            else if (i!=(inedge^1))
                low[x]=min(low[x],dfn[y]);
        }
    }
    int c[maxn],Out[maxn],dcc;
    inline void dfs(int x)
    {
        c[x]=dcc;
        for (int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if (c[y] || bridge[i]) continue;
            dfs(y);
        }
    }
    int main()
    {
        freopen("rpaths.in","r",stdin);
        freopen("rpaths.out","w",stdout);
        int n,m;read(n);read(m);
        for (int i=1;i<=m;++i)
        {
            int x,y;
            read(x);read(y);
            add(x,y);add(y,x);
        }
        for (int i=1;i<=n;++i)
            if (!dfn[i]) tarjan(i,-1);
        for (int i=1;i<=n;++i)
            if (!c[i]) ++dcc,dfs(i);
        for (int i=2;i<=len;i+=2)
        {
            int x=ver[i^1],y=ver[i];
            if (c[x]!=c[y])
                ++Out[c[x]],++Out[c[y]];
        }
        int ans=0;
        for (int i=1;i<=dcc;++i)
            if (Out[i]==1) ++ans;
        printf("%d
    ",(ans+1)>>1);
        return 0;
    }
    View Code

    第二题:逃不掉的路

    题目描述
    现代社会,路是必不可少的。任意两个城镇都有路相连,而且往往不止一条。但有些路连年被各种XXOO,走着很不爽。按理说条条大路通罗马,大不了绕行其他路呗——可小撸却发现:从a城到b城不管怎么走,总有一些逃不掉的必经之路。
    他想请你计算一下,a到b的所有路径中,有几条路是逃不掉的?
    输入格式
    第一行是n和m,用空格隔开。
    接下来m行,每行两个整数x和y,用空格隔开,表示x城和y城之间有一条长为1的双向路。
    第m+2行是q。接下来q行,每行两个整数a和b,用空格隔开,表示一次询问。
    输出格式
    对于每次询问,输出一个正整数,表示a城到b城必须经过几条路。
    样例输入
    5 5
    1 2
    1 3
    2 4
    3 4
    4 5
    2
    1 4
    2 5
    样例输出
    0
    1
    样例解释
    第1次询问,1到4的路径有 1–2--4 ,还有 1–3--4 。没有逃不掉的道路,所以答案是0。
    第2次询问,2到5的路径有 2–4--5 ,还有 2–1--3–4--5 。必须走“4–5”这条路,所以答案是1。
    数据约定与范围
    共10组数据,每组10分。
    有3组数据,n ≤ 100 , n ≤ m ≤ 200 , q ≤ 100。
    另有2组数据,n ≤ 103, n ≤ m ≤ 2 x 103 , 100 < q ≤ 105。
    另有3组数据,103 < n ≤ 105 , m = n-1 , 100 < q ≤ 105。
    另有2组数据,103 < n ≤ 105 , n ≤ m ≤ 2 x 105 , 100 < q ≤ 105。
    对于全部的数据,1 ≤ x,y,a,b ≤ n;对于任意的道路,两端的城市编号之差不超过104;
    任意两个城镇都有路径相连;同一条道路不会出现两次;道路的起终点不会相同;查询的两个城市不会相同。

    分析:既然是求必须经过的边,那么边双包含的集合肯定不是必须经过的,就可以把所有边双缩点;

    原图得到一颗树后,问题就变成了简单的求树上两点之间的距离。

    这个题就是lca+边双的题啦:

     第三题:矿场搭建

    BZOJ 2730
    LUOGU 3225

    首先我们知道,对于这张图,我们可以枚举坍塌的是哪个点,对于每个坍塌的点,最多可以将图分成若干个不连通的块,这样每个块我们可能需要一个出口才能满足题目的要求,枚举每个坍塌的点显然是没有意义的,我们只需要每个图的若干个割点,这样除去割点的图有若干个块,我们可以求出只与一个割点相连的块,这些块必须要一个出口才能满足题目的要求,每个块内有块内个数种选法,然后将所有满足一个割点相连的块的点数连乘就行了。

    对于每个与一个割点相连的块必须建出口可以换一种方式理解,我们将每个块看做一个点,那么算上割点之后,这张图就变成了一颗树,只有叶子节点我们需要建立出口,因为对于非叶子节点我们不论断掉哪个点我们都有另一种方式相连,这里的叶子节点就是与一个割点相连的块。
    最后还有个特判,就是对于一个双连通图,我们至少需要选取两个点作为出口,因为如果就选一个,可能该点为坍塌点,这时我们就任选两个点就行了,方案数为点数x(点数-1)>>1。

    代码该如何写?
    先tarjan求一下所有的点双。
    然后对于每一个点双,分类讨论:
    1、只有一个割点,必须选一个非割点。
    2、有>=2个割点,不用选
    3、有0个割点,必须选俩。
    画个图理解一下撒;

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=510;
    struct gg
    {
        int y,next;
    }a[maxn*maxn];
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1,ch=getchar();
        while (!isdigit(ch)) ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    long long hl,ans=1;
    int lin[maxn],len;
    inline void add(int x,int y)
    {
        a[++len].y=y;
        a[len].next=lin[x];
        lin[x]=len;
    }
    int dfn[maxn],low[maxn],num,root;
    bool cut[maxn];
    inline void tarjan(int x,int fa)
    {
        dfn[x]=low[x]=++num;
        int flag=0;
        for (int i=lin[x];i;i=a[i].next)
        {
            int y=a[i].y;
            if (!dfn[y])
            {
                tarjan(y,x);
                low[x]=min(low[x],low[y]);
                if(low[y]>=dfn[x])
                {
                    ++flag;
                    if (x!=root||flag>1) cut[x]=1;
                }
            }
            else if (y!=fa)
                low[x]=min(low[x],dfn[y]);
        }
    }
    int vis[maxn],k,cnt;
    inline void dfs(int x)
    {
        vis[x]=k;
        ++cnt;
        for(int i=lin[x];i;i=a[i].next)
        {
            int y=a[i].y;
            if(vis[y]!=k&&cut[y]) ++num,vis[y]=k;
            if(!vis[y])    dfs(y);
        }
    }
    int main()
    {
        freopen("input.in","r",stdin);
        freopen("output.out","w",stdout);
        for (int ca=1;;++ca)
        {
            int n=0,m;
            read(m);
            if(!m) exit(0);
            memset(dfn,0,sizeof(dfn));
            memset(vis,0,sizeof(vis));
            memset(cut,0,sizeof(cut));
            memset(lin,0,sizeof(lin));
            hl=k=num=len=0;
            ans=1;
            for (int i=1;i<=m;++i)
            {
                int x,y;read(x);read(y);
                n=max(n,max(x,y));
                add(x,y);add(y,x);
            }
            for (int i=1;i<=n;++i)
                if(!dfn[i]) root=i,tarjan(i,i);
            for (int i=1;i<=n;++i)
                if(!vis[i]&&!cut[i])
                {
                    ++k,cnt=num=0;
                    dfs(i);
                    if(!num) hl+=2,ans*=cnt*(cnt-1)/2;
                    if(num==1) hl++,ans*=cnt;
                }
            printf("Case %d: %lld %lld
    ",ca,hl,ans);
        }
        return 0;
    }
    View Code

  • 相关阅读:
    OSI模型白话
    并发
    初始化与清理
    多线程
    recyclerview Adapter
    recyclerview刷新
    surfaceview
    viewgroup绘制流程
    view配置
    项目遇到的问题
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/10426016.html
Copyright © 2011-2022 走看看