位运算
常用的两种位运算的操作:
1.求n的第k位数字是几:n >> k & 1
内部步骤:
(1)先把第k位移到最后一位: n >> k
(2)看第k位是几: x & 1
2.返回n的最后一位1:lowbit(n) = n & -n
lowbit(x) : 返回x的最后一位1
eg: x = 10,二进制就是1010 ·lowbit(x) = 2,二进制为10
lowbit(x) 就相当于 x & -x
x & -x == x & (~x + 1) //~为取反的意思
这个可以用于判断一个数的二进制里有多少个1
代码如下:
#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++;
cout << res << endl;
}
return 0;
}
离散化
概念:当部分场景需要用到数组的下标来处理问题时而又给的下标值过大时我们就需要利用离散化来将下标值映射到另一个数值的值来进行储存。
eg: 值域0 ~ 1e9, 个数1e5.
a[ ] : 1, 3, 5 , 200, 5000......
映射: 0, 1, 2 , 3 , 4........
需要处理的问题:
1.a[ ]中可能存在重复的元素 方法:去重
2.如何算出x离散化后的值 方法:二分
去重的模板:
vector<int> alls;//存储所有待离散化的值
sort(alls.begin(), alls.end());//将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());//去掉重复元素
//unique的作用:将数组所有不同的值放在数组的前端,并返回最后一个不同的值后面一个数的位置
例题:
假定有一个无限长的数轴,数轴上每个坐标上的数都是0。
现在我们首先进行n次操作,每次操作将某一个位置x上的数加上c。
然后进行m次询问,每次询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和
思路:将题目所提到的所有坐标都给离散化,最后将值存入a[n]数组中,最后根据询问的内容来进行前缀和处理得到答案。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 3e6 + 10;
int n, m;
int a[N], s[N]; //s为前缀和
vector<int> alls;
vector<PII> add, query;
//求x的值离散化后的结果
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 < n; i++) {
int l, r;
cin >> l >> r;
query.push_back({ l, r });
alls.push_back(l);//将每个坐标都放入待离散化的数组中
alls.push_back(r);
}
//以上两个for循环就可以将所有题目所提到的坐标都放入待离散化的数组中。
//去重
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 <= alls.size(); i++) s[i] = s[i - 1] + a[i];
//处理访问
for (auto item : query) {
int l = find(item.first);
int r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
区间合并
概念:存在相同范围的两个区间可以进行合并。
步骤:
1.按照区间左端点进行排序
2.进行合并并且储存
例题:
题意:在可以进行区间合并的情况下,那么会剩下多少个不相交的区间。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n;
vector<PII> segs;
void merge(vector<PII>& segs)
{
vector<PII> res;
//排序
//c++中sort对pair排序是优先对左边的进行排序
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的出现是防止题目出现空集的情况
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;
}