算法基础_基础算法【位运算 + 离散化 + 区间合并】

算法基础_基础算法【位运算 + 离散化 + 区间合并】

往期《算法基础》回顾:
算法基础_基础算法【快速排序 + 归并排序 + 二分查找】
算法基础_基础算法【高精度 + 前缀和 + 差分 + 双指针】

---------------位运算---------------

801.二进制中1的个数

题目介绍

方法一:

cpp 复制代码
#include <iostream>
using namespace std;

int lowbit(int x)
{
	return x & -x;
}

int main()
{
	int n; //整数的个数
	cin >> n;
	while (n--)
	{
		int x; //整数的值
		cin >> x;

		int res = 0;
		while (x)
		{
			x -= lowbit(x);
			res++; // 每次减去x的最后一位1
		}
		cout << res << " ";
	}
	return 0;
}
//位运算:
//求n的第k位数字:n >> k & 1
//返回n的最后一位1:lowbit(n) = n & -n

代码片段解释

片段一:

cpp 复制代码
int lowbit(int x)
{
	return x & -x;
}

lowbit(x):用于返回 x 的二进制表示中,最低位的 1 及其后面的所有 0 所组成的二进制数对应的十进制值。

  • 如果 x = 6(二进制 110),lowbit(x) 返回 2(二进制 10
  • 如果 x = 12(二进制 1100),lowbit(x) 返回 4(二进制 100

x & -x 的原理:

  • -xx补码表示 。在计算机中,负数的表示方式是取反加 1 (即:补码)

    • 例如:x = 6(二进制 0000 0110),-x 的计算过程:
      1. 取反:1111 1001
      2. 加 1:1111 1010(即:-6 的二进制表示)
  • x & -x 的结果是 x 的二进制表示中最低位的 1 及其后面的所有 0

    • 例如:x = 6(二进制 0000 0110),-x = 1111 1010
    • x & -x 的结果是 0000 0010(二进制 10,即:2
      疑问:为什么 x & -x 能得到最低位的 1
  • x 的二进制表示中,最低位的 1 右边的所有位都是 0

  • -xx 的补码,它的二进制表示中,最低位的 1 及其右边的位与 x 相同 ,而左边的位都取反

  • x-x 进行按位与运算时:

    • 最低位的 1 及其右边的 0 会被保留
    • 左边的位会因为取反操作而全部变为 0
  • 因此x & -x 的结果就是 x 的最低位的 1 及其后面的所有 0


示例1

  • x = 6(二进制 0000 0110

  • -x = -6(二进制 1111 1010

  • x & -x

    cpp 复制代码
      0000 0110  (x)
    & 1111 1010  (-x)
    ------------
      0000 0010  (结果)
  • 结果是 2(二进制 10

示例2

  • x = 12(二进制 0000 1100

  • -x = -12(二进制 1111 0100

  • x & -x

    cpp 复制代码
      0000 1100  (x)
    & 1111 0100  (-x)
    ------------
      0000 0100  (结果)
  • 结果是 4(二进制 100

片段二:

cpp 复制代码
int res = 0;
while (x)
{
	x -= lowbit(x);
	res++; // 每次减去x的最后一位1
}

代码的作用

  • 这行代码的作用是:统计整数 x 的二进制表示中 1 的个数
  • 通过不断去掉 x 的二进制表示中最低位的 1,并计数,直到 x 变为 0

代码的组成

  • while (x)

    • 这是一个循环条件,表示只要 x 不为 0,循环就会继续执行
    • x 变为 0 时,循环结束
  • x -= lowbit(x)

    • lowbit(x) 返回 x 的二进制表示中最低位的 1 所对应的值
    • x -= lowbit(x) 表示将 x 减去其最低位的 1,相当于去掉了 x 的二进制表示中最低位的 1
  • res++

    • 每次去掉一个 1,计数器 res 加 1
    • 最终 res 的值就是 x 的二进制表示中 1 的个数

示例 :假设 x = 13,其二进制表示为 1101

  1. 第一次循环

    • x = 13(二进制 1101
    • lowbit(13) = 1(二进制 0001
    • x -= lowbit(x)x = 13 - 1 = 12(二进制 1100
    • res++res = 1
  2. 第二次循环

    • x = 12(二进制 1100
    • lowbit(12) = 4(二进制 0100
    • x -= lowbit(x)x = 12 - 4 = 8(二进制 1000
    • res++res = 2
  3. 第三次循环

    • x = 8(二进制 1000
    • lowbit(8) = 8(二进制 1000
    • x -= lowbit(x)x = 8 - 8 = 0(二进制 0000
    • res++res = 3
  4. 循环结束

    • x = 0,循环结束
    • 最终 res = 3,表示 13 的二进制表示中有 31

---------------离散化---------------

802.区间和

题目介绍

方法一:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int, int> PII;
const int N = 300010;

int n, m;  //操作的次数n,访问的次数m
int a[N], s[N]; //a[N]用于存储离散化后的数轴上的值,s[N]是前缀和数组
vector<int> alls; // alls用于存储所有需要离散化的位置
vector<PII> add, query; //add存储所有操作,query存储所有查询

int find(int x)
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
    {
        int x, c;
        cin >> x >> c;
        add.push_back({ x, c });
        alls.push_back(x);
    }

    for (int i = 0; i < m; i++)
    {
        int l, r;
        cin >> l >> r;
        query.push_back({ l, r });
        alls.push_back(l);
        alls.push_back(r);
    }

    // 去重
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());

    // 处理插入
    for (auto item : add)
    {
        int x = find(item.first);
        a[x] += item.second;
    }

    // 构造前缀和数组
    for (int i = 1; i <= a.size(); i++) s[i] = s[i - 1] + a[i];

    // 处理询问
    for (auto item : query)
    {
        int l = find(item.first), r = find(item.second);
        cout << s[r] - s[l - 1] << endl;
    }

    return 0;
}

代码执行过程

输入样例

cpp 复制代码
3 3
1 2
3 6
7 5
1 3
4 6
7 8

解释

  • 操作部分
    • 在位置 1 上加 2
    • 在位置 3 上加 6
    • 在位置 7 上加 5
  • 查询部分
    • 查询区间 [1, 3] 的和
    • 查询区间 [4, 6] 的和
    • 查询区间 [7, 8] 的和

1. 初始化

定义变量和数据结构:

  • n = 3(操作次数)
  • m = 3(查询次数)
  • add:存储操作。
  • query:存储查询。
  • alls:存储所有需要离散化的位置。

2. 读取操作

  • 读取 3 个操作:

    1. x = 1c = 2
      • {1, 2} 加入 add
      • 1 加入 alls
    2. x = 3c = 6
      • {3, 6} 加入 add
      • 3 加入 alls
    3. x = 7c = 5
      • {7, 5} 加入 add
      • 7 加入 alls
  • 此时:

    • add = [{1, 2}, {3, 6}, {7, 5}]
    • alls = [1, 3, 7]

3. 读取查询

  • 读取 3 个查询:

    1. l = 1r = 3
      • {1, 3} 加入 query
      • 13 加入 alls
    2. l = 4r = 6
      • {4, 6} 加入 query
      • 46 加入 alls
    3. l = 7r = 8
      • {7, 8} 加入 query
      • 78 加入 alls
  • 此时:

    • query = [{1, 3}, {4, 6}, {7, 8}]
    • alls = [1, 3, 7, 1, 3, 4, 6, 7, 8]

4. 离散化

  • alls 进行排序和去重:

    • 排序后:[1, 1, 3, 3, 4, 6, 7, 7, 8]
    • 去重后:[1, 3, 4, 6, 7, 8]
  • 离散化后的映射关系:

    • 1 -> 1
    • 3 -> 2
    • 4 -> 3
    • 6 -> 4
    • 7 -> 5
    • 8 -> 6

5. 处理操作

  • 遍历 add,将操作的值加到离散化后的位置上:

    1. {1, 2}
      • 离散化后位置:find(1) = 1
      • a[1] += 2
    2. {3, 6}
      • 离散化后位置:find(3) = 2
      • a[2] += 6
    3. {7, 5}
      • 离散化后位置:find(7) = 5
      • a[5] += 5
  • 此时:

    • a = [0, 2, 6, 0, 0, 5, 0]

6. 构造前缀和数组

  • 计算前缀和数组 s

    • s[0] = 0
    • s[1] = s[0] + a[1] = 0 + 2 = 2
    • s[2] = s[1] + a[2] = 2 + 6 = 8
    • s[3] = s[2] + a[3] = 8 + 0 = 8
    • s[4] = s[3] + a[4] = 8 + 0 = 8
    • s[5] = s[4] + a[5] = 8 + 5 = 13
    • s[6] = s[5] + a[6] = 13 + 0 = 13
  • 此时:

    • s = [0, 2, 8, 8, 8, 13, 13]

7. 处理查询

  • 遍历 query,计算每个查询的结果:
    1. {1, 3}
      • 离散化后:l = find(1) = 1r = find(3) = 2
      • 结果:s[2] - s[0] = 8 - 0 = 8
    2. {4, 6}
      • 离散化后:l = find(4) = 3r = find(6) = 4
      • 结果:s[4] - s[2] = 8 - 8 = 0
    3. {7, 8}
      • 离散化后:l = find(7) = 5r = find(8) = 6
      • 结果:s[6] - s[4] = 13 - 8 = 5

8. 输出结果

  • 查询结果:
    • 8(区间 [1, 3] 的和)
    • 0(区间 [4, 6] 的和)
    • 5(区间 [7, 8] 的和)

代码片段解释

片段一:

cpp 复制代码
const int N = 300010;

在代码中,N = 300010 的定义是为了为离散化后的数组和前缀和数组分配足够的空间

疑问:为什么将 N的值设置为300010呢?


  • 题目中涉及的操作和查询次数最多为 (10^5) 次
  • 每次操作会涉及一个位置 (x),每次查询会涉及两个位置 (l) 和 (r)
  • 因此,所有需要离散化的位置的总数最多为:
    • 操作次数:(n)(最多 (10^5))
    • 查询次数:(m)(最多 (10^5)),每次查询涉及两个位置
    • 总位置数:(n + 2m \leq 10^5 + 2 \times 10^5 = 3 \times 10^5)

片段二:

cpp 复制代码
typedef pair<int, int> PII;

typedef pair<int, int> PII; 这行代码的作用是pair<int, int> 定义一个别名 PII ,使得代码中可以更方便地使用 PII 来代替 pair<int, int>


pair<int, int> 的作用

  • pair 是 C++ 标准库中的一个模板类,用于存储两个值(可以是相同类型或不同类型)
  • pair<int, int> 表示一个存储两个 int 类型值的对象
    • pair<int, int> p = {1, 2}; 定义了一个 pair 对象 p
    • 其中 p.first = 1p.second = 2

typedef 的作用

  • typedef 是 C++ 中的关键字,用于为已有的数据类型定义一个新的别名。
    • typedef int myInt; 定义了一个别名 myIntmyInt 等价于 int
    • 之后可以使用 myInt x = 10; 来定义一个 int 类型的变量

typedef pair<int, int> PII; 的含义

  • 这行代码为 pair<int, int> 定义了一个别名 PII
  • 之后可以使用 PII 来代替 pair<int, int>,使代码更简洁

解题思路分析

离散化的问题的思路步骤:

第一步:将所有涉及这个无限长数组位置的变量存放在存储所有需要离散化的位置的数组中

第二步:对这个存储所有需要离散化的位置的数组中的元素按升序排序

第三步:对这个存储所有需要离散化的位置的数组中的元素去重操作

第四步:书写find函数(核心)(意义:每次调用find函数寻找原无限数组中的下标,它都会返回一个该位置在离散化后的数组中的下标)

第五步:处理加数操作

第六步:构造前缀和数组

第七步:处理访问操作

---------------区间合并---------------

803.区间合并

题目介绍

方法一:

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int n;
vector<pair<int, int>> segs;

void merge(vector<pair<int, int>>& segs)
{
	vector<pair<int, int>> res;
	sort(segs.begin(), segs.end());

	int st = -2e9, ed = -2e9;
	for (auto seg : segs)
	{
		if (ed < seg.first) //新/旧区间无交集
		{
			if (st != -2e9) res.push_back({ st, ed });
			st = seg.first, ed = seg.second;
		}
		else ed = max(ed, seg.second);//新/旧区间有交集
	}

	if (st != -2e9) res.push_back({ st, ed });

	segs = res;
}

int main()
{
	cin >> n;

	for (int i = 0; i < n; i++)
	{
		int l, r;
		cin >> l >> r;
		segs.push_back({ l, r });
	}

	merge(segs);

	cout << segs.size() << endl;
	return 0;
}

代码片段解释

片段一:

cpp 复制代码
int st = -2e9, ed = -2e9;

if (st != -2e9) res.push_back({ st, ed });

在这段代码中,int st = -2e9, ed = -2e9;if (st != -2e9) 的作用是初始化区间合并的起始和结束位置,并确保在合并区间时正确处理第一个区间


int st = -2e9, ed = -2e9; 的作用:

  • 初始化区间合并的起始和结束位置

    • sted 分别表示当前合并区间的起始和结束位置。
    • 初始值设置为 -2e9(即 (-2 \times 10^9)),这是一个极小的值,确保任何实际的区间都会大于这个初始值。
  • 为什么选择 -2e9

    • 题目中给定的区间范围是 ([-10^9, 10^9]),因此 -2e9 是一个比最小可能值更小的值,确保不会与任何实际区间冲突。
    • 这样做的目的是在合并区间时,能够正确识别和处理第一个区间。

if (st != -2e9) 的作用:

  • 确保第一个区间被正确处理

    • 在合并区间时,sted 初始值为 -2e9,表示还没有开始处理任何区间。
    • 当遇到第一个区间时,sted 会被更新为该区间的起始和结束位置。
    • 在后续的循环中,如果 sted 仍然是 -2e9,说明还没有处理任何区间,此时不需要将 {st, ed} 加入结果中。
  • 防止将无效区间加入结果

    • 如果 sted 仍然是 -2e9,说明还没有处理任何区间,此时不需要将 {st, ed} 加入结果中。
    • 只有当 sted 被更新为实际的区间值时,才将 {st, ed} 加入结果中。

解题思路分析

代码的逻辑流程:

1. 初始化

  • st = -2e9, ed = -2e9:表示当前没有处理任何区间。

2. 遍历区间

  • 遍历所有区间 segs,逐个处理:
    1. 如果当前区间与合并区间无交集
      • 将当前的合并区间 {st, ed} 加入结果 res(如果 sted 不是初始值)
      • 更新 sted 为当前区间的起始和结束位置。
    2. 如果当前区间与合并区间有交集
      • 更新合并区间的结束位置 ed 为当前区间和合并区间的最大值。

3. 处理最后一个区间

  • 在循环结束后,如果 sted 不是初始值,将最后一个合并区间 {st, ed} 加入结果 res

示例说明:

输入

cpp 复制代码
3
1 3
2 6
5 7
  1. 初始化

    • st = -2e9, ed = -2e9

    • res = []

  2. 处理第一个区间 [1, 3]

    • ed < seg.first-2e9 < 1),说明当前没有处理任何区间。

    • {st, ed}(无效区间)不加入结果。

    • 更新 st = 1, ed = 3

  3. 处理第二个区间 [2, 6]

    • ed >= seg.first3 >= 2),说明当前区间与合并区间有交集。

    • 更新 ed = max(ed, seg.second) = max(3, 6) = 6

  4. 处理第三个区间 [5, 7]

    • ed >= seg.first6 >= 5),说明当前区间与合并区间有交集。

    • 更新 ed = max(ed, seg.second) = max(6, 7) = 7

  5. 处理结束

    • 将最后一个合并区间 {1, 7} 加入结果 res

输出

cpp 复制代码
1

区间合并步骤总结:

第一步:定义一个与要处理的容器一样的容器用于临时存放合并后的区间

第二步:将传入的容器中的元素按升序排列

第三步:定义两个变量作为初始区间的两端点,其值为区间不可能达到最小值

第四步:使用范围for循环遍历排好序的容器中的每一个元素

  • 第五步:使用if分支语句判断新区间是否与旧区间有交集

    • 第六步:无交集同时还是真实的区间的话,就将旧区间添加到临时容器
    • 第七步:无交集更新维护的区间为新区间
  • 第八步:有交集的话更新维护区间的右端点

第九步:使用if语句判断如果当前维护的区间是真实的区间的话,就将这个区间添加到临时容器

第十步:将临时容器中的元素添加到原容器中

相关推荐
whltaoin1 小时前
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
java·算法
同勉共进2 小时前
虚函数表里有什么?(二)——普通单继承下的虚函数表
c++·单继承·虚函数表·dynamic_cast·rtii
梭七y3 小时前
【力扣hot100题】(032)排序链表
算法·leetcode·链表
SsummerC3 小时前
【leetcode100】数组中的第K个最大元素
python·算法·leetcode
编程绿豆侠3 小时前
力扣HOT100之链表:206. 反转链表
算法·leetcode·链表
永恒迷星.by4 小时前
文件操作(c语言)
c语言·c++·算法·文件操作
还有你Y4 小时前
MIMO预编码与检测算法的对比
算法·预编码算法
凯强同学5 小时前
第十四届蓝桥杯大赛软件赛省赛Python 大学 C 组:7.翻转
python·算法·蓝桥杯
一个真正のman.5 小时前
c加加学习之day01
学习
蔗理苦5 小时前
2025-04-03 Latex学习1——本地配置Latex + VScode环境
ide·vscode·学习·latex