闇の連鎖-

闇の連鎖


题目描述

image-20210728165603872


核心思路

题意:有 n − 1 n-1 n1条主要边构成一棵树,然后有 m m m条附加边,当把其中一条附加边添加到主要边构成的树中,则会与树上 x , y x,y x,y之间的路径上一起形成一个环。我们需要砍掉一条主要边和一条附加边,使得这棵树不再连通,成为两个独立的部分。我们需要统计有多少种不同的方案,使得这棵树不连通。

显然, “主要边” 构成一棵树,而一条 “附加边” 必然会和其两端的LCA形成环,如图所示:

image-20210728170420786

那么,对于每一条主要边,存在三种情况:

  1. 没有被任何环覆盖

    如下图所示:红色表示附加边,粉红色表示一条主要边,这条主要边并不在 这条附加边所形成的环中

    image-20210728170657069

  2. 只被一个环给覆盖

    如下图所示:红色表示附加边,粉红色表示一条主要边,这条主要边在 这条附加边所形成的环中

    image-20210728170817976

  3. 被2个及以上的环覆盖

    如下图所示:红色表示附加边,粉红色表示一条主要边,这条主要边在 两条附加边所形成的两个环中

    image-20210728170943256

  • 对于情况一,枚举的那个主要边并不在环中,很明显,我们只要把这条主要边删除,那么这个图必然是不连通的。由于题目要求必须砍掉一条主要边和砍掉一条附加边。现在砍掉了这条枚举到的主要边,那么还需要砍掉一条附加边。由于有 m m m条附加边,因此我们有 m m m种选择,所以此时就有 m m m种方案。所以让答案ans累加上m即可, a n s + = m ans+=m ans+=m
  • 对于情况二,枚举的那个主要边在环中,可以发现,如果我们把这条主要边删除,要想让图不连通,必然还要再删除这条附加边,因此这是唯一的一种方案了(即必然是砍掉这条枚举到的主要边和这个附加边),所以让答案ans累加上1即可, a n s + = 1 ans+=1 ans+=1
  • 对于情况三,枚举的那个主要边在两个及以上的环中,以两个环为栗子,可以发现,如果我们把这条主要边砍掉,即使再砍掉一条附加边,这张图仍然是连通的,因此必须砍掉两条附加边,才能使得这张图变得不连通,但是题目要求只能砍掉一条主要边和砍掉一条附加边。因此,对于这种情况,不能得出方案,所以不需要累加答案。

那么我们怎么统计每条边被环覆盖的次数呢?也就是说,我们如何统计附加边 ( x , y ) (x,y) (x,y)所在的那个环中每条边上的权值呢?每条边上的权值表示被附加边覆盖的次数。

也就是我们如何让从点 x x x出发经过它俩的lca然后到达节点 y y y所经过的每一条边都+c呢?这其实可以用树上差分来做。

d [ x ] d[x] d[x] d [ y ] d[y] d[y] 会对它们到根节点上的每一条边都+c, d [ l c a ] d[lca] d[lca]会对它们到根节点上的每一条边都-2c,那么这样最终的效果就是:让 x x x到lca中和 y y y到lca中的每条边都+c

image-20210728180813869

树上差分可以分为以下三种:

  • 按点差分
  • 按边差分
  • 按深度差分

我们这里是按边差分,因为我们想知道每条边上的覆盖次数(可以认为是这条边的边权)。但是呢,一般按边差分不好做,我们都是将边权转换为点权,然后用按点差分来做。我们知道,每个节点最多只有1个父节点,也就是向上连的边只有1条,所以我们把一条边的边权下放到它的子节点的点权上(注意根节点没有点权,因为根节点上面没有边,所以没有边权下放给根节点),这样我们就转化到按点差分了。我们可以采用DFS对这棵树进行深搜,在回溯时,把子树 v v v中的所有节点上的权值都累加到节点 u u u上,设总和为 s u m sum sum,节点 v v v的父节点是 u u u,那么 s u m sum sum其实就是节点 u u u和节点 v v v之间的边权了,那么也就是覆盖次数了。

如下图所示

image-20210728181112585

以节点 z z z所在的子树为栗子,将节点 z z z下面的所有点权 d [ i ] d[i] d[i]累加然后统计到节点 z z z上,设总和为 s u m sum sum,那么节点 p p p和节点 z z z之间的边权其实就是 s u m sum sum

注意点:对于数组fa[][],因为点数最多是1e5,由于 2 16 < 1 e 5 < 2 17 2^{16}<1e5<2^{17} 216<1e5<217,所以第二维的大小应该取到17,这样才能弄完全部1e5个点。因此 f a fa fa的第二维有0~16一共17个数,第二维需要设置为17。

虽然说题目中有附加边,但是我们建图时,只是把主要边给建立出来,并没有把附加边建到图中去。由于最多有 N N N个点,这是树,是无向边,因此最多有 M = 2 × N M=2\times N M=2×N条边。

算法设计:

  • 先将主要边用图建立出来
  • 进行bfs预处理出fa[][]
  • 枚举每一条附加边,对于附加边所在环上的所有主要边其两端的点权都+1,预处理出差分数组d[]
  • 进行dfs,枚举每一条主要边,进行树上差分得到每条主要边上的权值。查看每条主要边上的权值:
    • 如果 c = 0 c=0 c=0,则说明这条主要边并不在附加边所形成的环中,就是情况一,所以答案 a n s + = m ans+=m ans+=m
    • 如果 c = 1 c=1 c=1,则说明这条主要边在附加边所形成的环中,就是情况二,所以答案 a n s + = 1 ans+=1 ans+=1
    • 如果 c > 1 c>1 c>1,则说明这条主要边处于多条附加边所形成的多个环中,就是情况三,不用累加答案

代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,M=2*N;
int n,m;    //点数 附加边数
int h[N],e[M],ne[M],w[M],idx;
int depth[N];
int d[N];   //存储每个点的差分 树上差分,每个点上的权值,可以转化为边上的权值
int q[N];   //bfs使用到的队列
int fa[N][17];  
int ans;    //方案数
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//预处理出fa[][]
void bfs(int root)
{
    int hh=0,tt=0;
    memset(depth,0x3f,sizeof depth);
    depth[0]=0,depth[root]=1;
    q[0]=root;
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(depth[j]>depth[t]+1)
            {
                depth[j]=depth[t]+1;
                q[++tt]=j;
                fa[j][0]=t;
                for(int k=1;k<=16;k++)
                    fa[j][k]=fa[fa[j][k-1]][k-1];
            }
        }
    }
}
int LCA(int a,int b)
{
    if(depth[a]<depth[b])
        swap(a,b);
    for(int k=16;k>=0;k--)
    {
        if(depth[fa[a][k]]>=depth[b])
        {
            a=fa[a][k];
        }
    }
    if(a==b)
        return a;
    for(int k=16;k>=0;k--)
    {
        if(fa[a][k]!=fa[b][k])
        {
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    return fa[a][0];
}
//返回以节点u为根的子树中各节点的权值总和
int dfs(int u,int father)
{
    int res=d[u];   //记录节点u的点权
    //遍历节点u的所有邻接点
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i]; //邻接点j
        if(j!=father)   ///避免向上重复搜索已经遍历过的边
        {
            //递归搜索以j为根的子树中个节点的权值总和
            int c=dfs(j,u);
            //最终在回溯时就会算出以节点u为根的子树中各节点的权值总和c
            //而这个c其实就是节点u与它的父节点之间的边权
            if(c==0)    //如果为0则说明这条主要边并不在附加边所形成的环中
                ans+=m;
            else if(c==1)//如果为1则说明这条主要边在附加边所形成的环中
                ans++;
            //相当于res=d[u]+dfs(j,u) 最开始res记录的是节点u的点权
            //而dfs(j,u)就是把节点u以下的全部节点的点权都累加到节点u身上
            //因此最终节点u的点权就是它自身拥有的d[u]+别上给它的c
            res+=c; 
        }
    }
    //返回以节点u为根的子树中各节点的权值总和
    //其实也就是返回节点u和它的父节点之间的这条边的边权 及被附加边覆盖的次数
    return res;
}
int main()
{
    int root=1; //树的根节点   我们设1号点为根节点  任意点都是可以的
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    for(int i=0;i<n-1;i++)  //读入n-1条 主要边
    {
        int a,b;
        scanf("%d%d",&a,&b);
        //建图时只包括主要边  而没有把附加边建出来
        add(a,b),add(b,a);  //无向图
    }
    //预处理出fa[][]
    bfs(root);
    //依次枚举每一条附加边
    for(int i=0;i<m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        //找到当前枚举的这条附加边的两个端点的最近公共祖先
        int lca=LCA(a,b);
        //差分
        d[a]++,d[b]++,d[lca]-=2;
    }
    dfs(1,-1);
    printf("%lld\n",ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值