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

#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 & -nint 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 的原理:
-x 是 x 的补码表示。在计算机中,负数的表示方式是取反加 1(即:补码) x = 6(二进制 0000 0110),-x 的计算过程: 1111 10011111 1010(即:-6 的二进制表示)x & -x 的结果是 x 的二进制表示中最低位的 1 及其后面的所有 0 x = 6(二进制 0000 0110),-x = 1111 1010x & -x 的结果是 0000 0010(二进制 10,即:2)疑问:为什么 x & -x 能得到最低位的 1?
x 的二进制表示中,最低位的 1 右边的所有位都是 0-x 是 x 的补码,它的二进制表示中,最低位的 1 及其右边的位与 x 相同,而左边的位都取反x 和 -x 进行按位与运算时: 1 及其右边的 0 会被保留0x & -x 的结果就是 x 的最低位的 1 及其后面的所有 0示例1:
x = 6(二进制 0000 0110)
-x = -6(二进制 1111 1010)
x & -x:
0000 0110 (x)
& 1111 1010 (-x)
------------
0000 0010 (结果)结果是 2(二进制 10)
示例2:
x = 12(二进制 0000 1100)
-x = -12(二进制 1111 0100)
x & -x:
0000 1100 (x)
& 1111 0100 (-x)
------------
0000 0100 (结果)结果是 4(二进制 100)
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 的二进制表示中最低位的 1res++:
1,计数器 res 加 1res 的值就是 x 的二进制表示中 1 的个数 示例:假设 x = 13,其二进制表示为 1101
x = 13(二进制 1101)lowbit(13) = 1(二进制 0001)x -= lowbit(x):x = 13 - 1 = 12(二进制 1100)res++:res = 1x = 12(二进制 1100)lowbit(12) = 4(二进制 0100)x -= lowbit(x):x = 12 - 4 = 8(二进制 1000)res++:res = 2x = 8(二进制 1000)lowbit(8) = 8(二进制 1000)x -= lowbit(x):x = 8 - 8 = 0(二进制 0000)res++:res = 3x = 0,循环结束res = 3,表示 13 的二进制表示中有 3 个 1
#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;
}输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8解释:
1 上加 23 上加 67 上加 5[1, 3] 的和[4, 6] 的和[7, 8] 的和1. 初始化
定义变量和数据结构:
n = 3(操作次数)m = 3(查询次数)add:存储操作。query:存储查询。alls:存储所有需要离散化的位置。2. 读取操作
3 个操作:
x = 1,c = 2: {1, 2} 加入 add1 加入 allsx = 3,c = 6: {3, 6} 加入 add3 加入 allsx = 7,c = 5: {7, 5} 加入 add7 加入 allsadd = [{1, 2}, {3, 6}, {7, 5}]alls = [1, 3, 7]3. 读取查询
3 个查询:
l = 1,r = 3: {1, 3} 加入 query1 和 3 加入 allsl = 4,r = 6: {4, 6} 加入 query4 和 6 加入 allsl = 7,r = 8: {7, 8} 加入 query7 和 8 加入 allsquery = [{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 -> 13 -> 24 -> 36 -> 47 -> 58 -> 65. 处理操作
add,将操作的值加到离散化后的位置上:
{1, 2}: find(1) = 1a[1] += 2{3, 6}: find(3) = 2a[2] += 6{7, 5}: find(7) = 5a[5] += 5a = [0, 2, 6, 0, 0, 5, 0]6. 构造前缀和数组
s:
s[0] = 0s[1] = s[0] + a[1] = 0 + 2 = 2s[2] = s[1] + a[2] = 2 + 6 = 8s[3] = s[2] + a[3] = 8 + 0 = 8s[4] = s[3] + a[4] = 8 + 0 = 8s[5] = s[4] + a[5] = 8 + 5 = 13s[6] = s[5] + a[6] = 13 + 0 = 13s = [0, 2, 8, 8, 8, 13, 13]7. 处理查询
query,计算每个查询的结果: {1, 3}: l = find(1) = 1,r = find(3) = 2s[2] - s[0] = 8 - 0 = 8{4, 6}: l = find(4) = 3,r = find(6) = 4s[4] - s[2] = 8 - 8 = 0{7, 8}: l = find(7) = 5,r = find(8) = 6s[6] - s[4] = 13 - 8 = 58. 输出结果
8(区间 [1, 3] 的和)0(区间 [4, 6] 的和)5(区间 [7, 8] 的和)const int N = 300010;在代码中,
N = 300010的定义是为了为离散化后的数组和前缀和数组分配足够的空间 疑问:为什么将 N的值设置为300010呢?
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 对象 pp.first = 1,p.second = 2 typedef 的作用
typedef 是 C++ 中的关键字,用于为已有的数据类型定义一个新的别名。 typedef int myInt; 定义了一个别名 myInt,myInt 等价于 intmyInt x = 10; 来定义一个 int 类型的变量 typedef pair<int, int> PII; 的含义
pair<int, int> 定义了一个别名 PIIPII 来代替 pair<int, int>,使代码更简洁离散化的问题的思路步骤: 第一步:将所有涉及这个无限长数组位置的变量存放在存储所有需要离散化的位置的数组中 第二步:对这个存储所有需要离散化的位置的数组中的元素按升序排序 第三步:对这个存储所有需要离散化的位置的数组中的元素去重操作 第四步:书写find函数(核心)(意义:每次调用find函数寻找原无限数组中的下标,它都会返回一个该位置在离散化后的数组中的下标) 第五步:处理加数操作 第六步:构造前缀和数组 第七步:处理访问操作

#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;
}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; 的作用:
st 和 ed 分别表示当前合并区间的起始和结束位置。-2e9(即 (-2 \times 10^9)),这是一个极小的值,确保任何实际的区间都会大于这个初始值。-2e9:
-2e9 是一个比最小可能值更小的值,确保不会与任何实际区间冲突。 if (st != -2e9) 的作用:
st 和 ed 初始值为 -2e9,表示还没有开始处理任何区间。st 和 ed 会被更新为该区间的起始和结束位置。st 和 ed 仍然是 -2e9,说明还没有处理任何区间,此时不需要将 {st, ed} 加入结果中。st 和 ed 仍然是 -2e9,说明还没有处理任何区间,此时不需要将 {st, ed} 加入结果中。st 和 ed 被更新为实际的区间值时,才将 {st, ed} 加入结果中。代码的逻辑流程:
1. 初始化
st = -2e9, ed = -2e9:表示当前没有处理任何区间。2. 遍历区间
segs,逐个处理: {st, ed} 加入结果 res(如果 st 和 ed 不是初始值)st 和 ed 为当前区间的起始和结束位置。ed 为当前区间和合并区间的最大值。3. 处理最后一个区间
st 和 ed 不是初始值,将最后一个合并区间 {st, ed} 加入结果 res示例说明:
输入:
3
1 3
2 6
5 7st = -2e9, ed = -2e9
res = []
[1, 3]:
ed < seg.first(-2e9 < 1),说明当前没有处理任何区间。
{st, ed}(无效区间)不加入结果。
st = 1, ed = 3
[2, 6]:
ed >= seg.first(3 >= 2),说明当前区间与合并区间有交集。
ed = max(ed, seg.second) = max(3, 6) = 6
[5, 7]:
ed >= seg.first(6 >= 5),说明当前区间与合并区间有交集。
ed = max(ed, seg.second) = max(6, 7) = 7
{1, 7} 加入结果 res输出:
1区间合并步骤总结: 第一步:定义一个与要处理的容器一样的容器用于临时存放合并后的区间 第二步:将传入的容器中的元素按升序排列 第三步:定义两个变量作为初始区间的两端点,其值为区间不可能达到最小值 第四步:使用范围for循环遍历排好序的容器中的每一个元素
第九步:使用if语句判断如果当前维护的区间是真实的区间的话,就将这个区间添加到临时容器 第十步:将临时容器中的元素添加到原容器中