1.位运算
位运算主要用于以下两个操作:
①求某个数n的第k为数字: n >> k & 1
②返回数字n的最后一位1(如0110100 → 100)lowbit(n) = n & -n
下面看一道位运算的问题
问题 二进制中1的个数
问题描述
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
数据范围
1≤n≤100000,
0≤数列中元素的值≤10^9
输入样例
5
1 2 3 4 5
输出样例
1 1 2 1 2
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int main(){
scanf("%d", &n);
for(int i = 0; i < n; i ++) {
int x;
cin >> x;
int cnt = 0;
while(x){
x -= x & (-x);
cnt ++;
}
printf("%d ", cnt);
}
return 0;
}
核心部分
x -= (x & -x)
每减一次相当于去掉x的二进制表示中末尾的一个1
2.离散化
离散化主要用在数据范围很大但数据量相对较少的情况
比如n的范围是10^9,但实际输入数的个数只有10^5,存储的时候如果直接开辟x数据范围大小的存储空间会大大影响,可将所有数排序后映射到符合x实际个数的存储空间上存储,这就是离散化
离散化主要分为以下三步:
①对待离散化的序列排序
②去重
③根据二分查找其对应离散化后对应的位置(下标)
下面看一道具体的问题
问题 区间和
问题描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r]之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9≤x≤10^9,
1≤n,m≤10^5,
−10^9≤l≤r≤10^9,
−10000≤c≤10000
输入样例
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例
8
0
5
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int a[N], s[N]; //a[N]表示离散化后的数组, s[N]表示前缀和
vector<PII> adds, query; //添加操作和查询操作
vector<int> nums; //存储待离散化的数据
int n, m;
//找到x离散化后的下标, 1-n
int find(int x){
int l = 0, r = nums.size() - 1;
while(l < r){
int mid = (l + r) / 2;
if(nums[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i ++){
int x, c;
scanf("%d%d", &x, &c);
adds.push_back({x, c});
nums.push_back(x);
}
for(int i = 0; i < m; i ++){
int l, r;
scanf("%d%d", &l, &r);
query.push_back({l, r});
nums.push_back(l);
nums.push_back(r);
}
//离散化
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
//处理添加操作
for(auto item: adds){
int x = item.first, num = item.second;
a[find(x)] += num;
}
//初始化前缀和数组
for(int i = 1; i <= nums.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;
}
注意
本题需要开辟300000的空间,因为待离散化的内容包括插入数的位置和查询的区间三个部分,每个部分数据量都是10^5
3.区间合并
区间合并指的是将一系列区间进行合并,若有交集的部分就合并,输出最后合并后单独的区间数量
看一道具体的问题
问题 区间合并
问题描述
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000,
−10^9≤l≤r≤10^9
输入样例
5
1 2
2 4
5 6
7 8
7 9
输入样例
3
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010;
int n;
vector<PII> segs;
vector<PII> merge(vector<PII> & t){
sort(t.begin(), t.end());
vector<PII> res;
if(t.size() == 0) return res;
int start = t[0].first, end = t[0].second;
for(int i = 1; i < t.size(); i ++){
int l = t[i].first, r = t[i].second;
if(l > end) {
res.push_back({l, r});
start = l, end = r;
}
else{
end = max(r, end);
}
}
//加入最后一个区间
res.push_back({start, end});
return res;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++){
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
vector<PII> res = merge(segs);
cout << res.size() << endl;
return 0;
}
思路
先按区间的左端点排序,维护一个start和end表示待合并区间的左右端点,若start更新时则将合并区间加入结果集和,否则更新end即可