每周一算法:树上差分

题目链接

闇の連鎖

题目描述

传说中的暗之连锁被人们称为Dark

Dark是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。

经过研究,你发现Dark呈现无向图的结构,图中有 N N N个节点和两类边,一类边被称为主要边,而另一类被称为附加边。

Dark N – 1 N–1 N–1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。
另外,Dark 还有 M M M 条附加边。

你的任务是把Dark斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。

但是你的能力只能再切断 Dark 的一条附加边。

现在你想要知道,一共有多少种方案可以击败 Dark

注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark

输入格式

第一行包含两个整数 N N N M M M
之后 N – 1 N–1 N–1 行,每行包括两个整数 A A A B B B,表示 A A A B B B 之间有一条主要边。
之后 M M M 行以同样的格式给出附加边。

输出格式

输出一个整数表示答案。

数据范围

【数据范围】
N ≤ 100000 , M ≤ 200000 N≤100000,M≤200000 N100000,M200000,数据保证答案不超过 2 31 − 1 2^{31}−1 2311

输入样例

4 1
1 2
2 3
1 4
3 4

输出样例

3

算法思想

根据题目描述,有 n n n个节点和 n − 1 n-1 n1条主要边,并且任意两个节点之间都存在一条只由主要边构成的路径,构成一棵树。此时无论切断哪条主要边,都可以将树斩为不连通的两个部分。

如果引入一条附加边,就会构成一个环,如下图所示:
在这里插入图片描述
此时,切断环中任意一条主要边,再切断该附加边就可以斩为不连通的两个部分。

如果再引入一条附加边,如下图所示:
在这里插入图片描述
可以发现:

  • 图中黄色的主要边存在于两个环中,如果切断一条黄色边,那么还要切断两条附加边,才能将图斩为不连通的两个部分,因此不满足题目中要求的:只能切断一条主要边、再切断一条附加边将图斩为不连通的两个部分。
  • 图中绿色或者红色的主要边只存在于一个环中,如果将其切断,那么只需要切断对应的附加边,就可以将图斩为不连通的两个部分
  • 图中蓝色的主要边不在环中,如果将其切断,那么再切断任意一条附件边,都可以将图斩为不连通的两个部分。

基于上述分析,可以对每一条通过附加边连接的主要边进行计数,如下图所示:
在这里插入图片描述
最后,计算出每条主要边的经过总次数 s s s

  • 如果s == 0,可以切断 m m m条附加边中任意一条满足要求,方案数ans += m
  • 如果s == 1,可以切断 1 1 1条附加边满足要求,方案数ans += 1
  • 如果s > 1,无法满足要求,方案数不变

树上差分

通过上面分析,需要将一条路径上的每一条主要边的经过次数都增加 1 1 1,一共要进行 m m m次操作,最终计算出经过每条边的总次数,朴素做法的时间复杂度为 O ( n × m ) O(n\times m) O(n×m),这里可以采用树上差分进行优化。

不妨设 d [ ] d[] d[]为树中每个节点向上连接的边经过次数的差分数组,如果要将从 u u u v v v的路径中每条边的经过次数 + 1 +1 +1,如下图所示:
在这里插入图片描述
可以让d[u] += 1d[v] += 1d[p] -= 2,其中 p p p u 、 v u、v uv的最近公共祖先。

经过若干次操作之后,再自底向上累加每棵子树的和,就可以求出每条边经过的总次数。这就是树上差分。

除此之外,在进行树上差分之前,需要预处理出两个节点的最近公共祖先,可以使用倍增求解,具体参考博主的另一篇文章——每周一算法:倍增法求最近公共祖先(LCA)

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, K = 17;
int n, m, depth[N], q[N], fa[N][K], d[N], ans = 0;
vector<int> g[N];
//预处理每个节点的深度和倍增数组
void bfs()
{
    memset(depth, 0x3f, sizeof depth);
    depth[0] = 0, depth[1] = 1;
    int hh = 0, tt = 0;
    q[0] = 1; //根节点入队
    while(hh <= tt)
    {
        int u = q[hh ++];
        for(int v : g[u])
        {
            if(depth[v] > depth[u] + 1)
            {
                depth[v] = depth[u] + 1;
                q[++ tt] = v;
                fa[v][0] = u; //v的父结点是u
                for(int i = 1; i < K; i ++) //计算v向上跳2^i的节点
                    fa[v][i] = fa[fa[v][i - 1]][i - 1];
            }
        }
    }
}
int lca(int u, int v)
{
    if(depth[u] < depth[v]) swap(u, v);
    for(int i = K - 1; i >= 0; i --)//将u跳到和v同层
    {
        if(depth[fa[u][i]] >= depth[v]) u = fa[u][i];
    }
    if(u == v) return u; //刚好相同
    for(int i = K - 1; i >= 0; i --) //将u和v同时向上跳
    {
        if(fa[u][i] != fa[v][i])
        {
            u = fa[u][i], v = fa[v][i];
        }
    }
    return fa[u][0];
}
int dfs(int u, int father)
{
    int res = d[u]; 
    for(int v : g[u])
    {
        if(v != father)
        {
            int s = dfs(v, u); //子树的经过总次数
            if(s == 0) ans += m; //如果v对应的边经过0次,方案数增加m
            else if(s == 1) ans += 1; 
            res += s; //累加子树的经过次数
        }
    }
    return res;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i < n; i ++) //n-1条边
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v), g[v].push_back(u); //无向图,双向建边
    }
    bfs(); //预处理每个节点的深度和倍增数组
    for(int i = 0; i < m; i ++) //输入附加边
    {
        int u, v;
        scanf("%d%d", &u, &v);
        int p = lca(u, v); //计算u和v的最近公共祖先
        d[u] ++, d[v] ++, d[p] -= 2; //树上差分
    }
    dfs(1, -1); //自底向上求树中每条边经过的总次数
    printf("%d\n", ans);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/531474.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用Python编写GUI程序实现WebP文件批量转换为JPEG格式

在Python编程中&#xff0c;经常会遇到需要处理图片格式的情况。最近&#xff0c;我遇到了一个有趣的问题&#xff1a;如何通过编写一个GUI程序来实现将WebP格式的图片批量转换为JPEG格式&#xff1f;在这篇博客中&#xff0c;我将分享我使用Python、wxPython模块和Pillow库实现…

打开Visual Studio后出现Visual Assist报错弹窗

安装了新的VA插件后发现无论如何清理打开VS都会报这个旧版VA报错弹窗&#xff0c;修复VS、重装VA都解决不了 后来进到VS安装目录&#xff0c;删掉一个可疑文件后弹窗再也不出现了

光伏电站运维管理平台功能分析

光伏电站的建设发展&#xff0c;不仅可以满足人们日益增长的用电需求&#xff0c;同时对于减少能源资源消耗也有着十分重要的作用。但是光伏电站因为区域跨度大&#xff0c;分布广泛等原因在建设发展中导致了人员管理困难、运维工作落实不到等问题&#xff0c;直接影响光伏电站…

【随笔】Git 高级篇 -- 相对引用1 main^(十二)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

python用循环新建多个列表

​在Python编程中&#xff0c;我们经常需要创建多个列表来存储和管理数据。有时候&#xff0c;列表的数量是已知的&#xff0c;我们可以手动逐一创建&#xff1b;但更多时候&#xff0c;列表的数量是动态的&#xff0c;或者我们希望通过某种模式来批量生成列表。这时候&#xf…

对称加密学习

对称加密是一种加密技术&#xff0c;它使用相同的密钥进行数据的加密和解密操作。这种加密方法因其高效性和速度优势&#xff0c;在数据加密领域得到了广泛的应用。 下面是两篇文章&#xff1a; AES加密学习-CSDN博客 加密算法学习-CSDN博客 推荐关注加密专栏&#xff1a; …

HDLbits 刷题 --Exams/m2014 q4g

Implement the following circuit: 实现以下电路 module top_module (input in1,input in2,input in3,output out);assign out (~(in1^in2))^in3; endmodule运行结果&#xff1a; 分析&#xff1a; 同或&#xff1a; out ~(in1 ^ in2); 异或取反 异或&#xff1a; out in1…

【设计模式】笔记篇

目录标题 OO设计原则策略模式 - Strategy定义案例分析需求思路分析核心代码展示进一步优化UML 图 观察者模式 - Observe定义案例分析需求UML图内置的Java观察者模式核心代码 总结 装饰者模式 - Decorator定义案例分析需求UML图分析核心代码 总结 工厂模式 - Abstract Method/Fa…

素人小红书发布如何选择账号?

如何从众多账号中筛选出符合品牌或产品特性、具有高性价比和合作潜力的账号&#xff0c;成为了许多品牌和营销人士关注的焦点。素人小红书发布如何选择账号&#xff1f;接下来伯乐网络传媒就来给大家分享一下&#xff0c;希望能为你在小红书上进行账号选择提供一些有价值的参考…

docker部署postgresql数据库和整合springboot连接数据源

公司想要把部分sqlserver的旧服务迁移到PG数据库&#xff0c;先写一个示例的demo&#xff0c;需要用docker部署postgresql数据库和整合springboot连接数据源 安装 下载最新镜像 docker pull postgres创建并且启动容器 docker run -it --name postgres --restart always -e …

嵌入式应会的模电数电基础

AC/DC交直流 电压 欧姆定律 常见元器件 电阻器 并联电阻&#xff0c;增加通路&#xff0c;电阻更小&#xff0c;电流更大 串联电阻&#xff0c;电阻更大&#xff0c;电流越小 相同阻值的电阻&#xff0c;个头大小不同主要区别在功率容量、耐压能力和散热性能方面。 功率容量…

【STL】priority_queue的底层原理及其实现

文章目录 priority_queue的介绍库中priority_queue的使用什么叫仿函数&#xff1f; 模拟实现prioprity_queue类 priority_queue的介绍 解释以上内容 priority_queue&#xff08;优先级队列&#xff09;跟stack、queue一样&#xff0c;都是一种容器适配器&#xff0c;根据严格的…

SpringBoot中定时任务踩坑,@Scheduled重复执行问题排查(看完直接破防)

前言 今天再开发业务需求的过程中&#xff0c;需要用到定时任务&#xff0c;原本定的是每10分钟推送一次&#xff0c;可是当每次十分钟到的时候&#xff0c;定时任务就会推送多条&#xff01;但是非常奇怪的是&#xff0c;本地调试的时候不会有问题&#xff0c;只有当你部署到…

OpenCV | 图像读取与显示

OpenCV 对图像进行处理时&#xff0c;常用API如下&#xff1a; API描述cv.imread根据给定的磁盘路径加载对应的图像&#xff0c;默认使用BGR方式加载cv.imshow展示图像cv.imwrite将图像保存到磁盘中cv.waitKey暂停一段时间&#xff0c;接受键盘输出后&#xff0c;继续执行程序…

windows 之 redis非安装版,启动与初始化密码

1、下载redis 免安装版 2、解压后&#xff0c;启动服务 3、双击客服端 4、设置密码 config set requirepass root123456成功后&#xff0c;退出服务再次双击 5、登录 再次执行命名时已经没权限了 使用 auth password 登录 成功后&#xff0c;就可以了 auth root123456 …

arcgis使用面shp文件裁剪线shp文件报错

水系数据裁剪&#xff0c;输出为空&#xff1a; ArcGIS必会的几个工具的应用 --提取、分割、融合、裁剪&#xff08;矢&#xff09;、合并、追加、镶嵌、裁剪&#xff08;栅&#xff09;、重采样_arcgis分割-CSDN博客 下面的方法都不行&#xff1a; ArcGIS Clip&#xff08;裁…

JavaScript - 你遇到过哪几种Javascript的错误类型

难度级别:中级及以上 提问概率:50% 我们在开发Javascript代码的时候,经常一不小心就会遇到各种各样的异常,浏览器也会及时给出错误信息,那么一般会遇到哪几种异常情况呢,我们来看一下。 1 ReferenceError错误 ReferenceError几乎是最…

Ubuntu 20.04.06 PCL C++学习记录(二十四)

[TOC]PCL中点云分割模块的学习 学习背景 参考书籍&#xff1a;《点云库PCL从入门到精通》以及官方代码PCL官方代码链接,&#xff0c;PCL版本为1.10.0&#xff0c;CMake版本为3.16&#xff0c;可用点云下载地址 学习内容 如何使用已知系数的 SAC_Models 从点云中提取参数模型…

Tomcat 获取客户端真实IP X-Forwarded-For

Tomcat 获取客户端真实IP X-Forwarded-For 代码实现&#xff1a; 在Host标签下面添加代码&#xff1a; <Valve className"org.apache.catalina.valves.RemoteIpValve" remoteIpHeader"x-forwarded-for" remoteIpProxiesHeader"x-forwarded-by&q…

面试算法-165-随机链表的复制

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…