位运算
二进制中1的个数
主要是介绍lowbit 运算
什么是lowbit?
lowbit运算是指获取一个二进制数中最右边的1所对应的数值。
具体来说,lowbit运算可以通过对一个数取反然后加1,再与原数进行按位与的方式来实现。例如,对于一个数x,其lowbit可以通过以下公式来计算:
lowbit(x) = x & (-x)
其中,-x是对x进行取反然后加1得到的结果。
例如,对于二进制数101100(十进制数为44),它的lowbit为100(十进制数为4)。这是因为最右边的1所对应的位是第三位,对应的数值为4,因此lowbit(x) = 4。
假设x的二进制表示中,最右边的1所在的位置是第k位(即第k位为1, 之后的都为0),那么:
- 对于x的二进制表示中,k位之右的数,它们在x中都对应了0,所以对于这部分的数值,x & (-x) = 0。
- 对于x的二进制表示中,k之左的数值,它们在x中对应了0 或者 1,而在-x的二进制表示中,他们对应 1 或 0, 与 x 的数值相反(0对1, 1对0)。因此,在进行按位与运算时,x中第k位之左的数值能够与-x中的相应位数值得到0。
- 对于第 k 位来说,x 的第k位为1,-x的第k为也为1,因此,在进行按位与运算时,x中第k位的数值能够与-x中的第k为数值得到1。
- 因此 x & (-x) = 2^k
由此可见,lowbit运算确实可以得到x二进制表示中最右边的1所对应的数值。
举个例子
x = (12)10 = (00000000000000000000000000001100)2
-x =(−12)10 = (11111111111111111111111111110100)2
x & (-x) = (00000000000000000000000000000100)2= (4)2
x - (x & -x) = (00000000000000000000000000001000) = (8)10
题目
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n 。第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。数据范围
1≤n≤100000 ,
0≤数列中元素的值≤109
输入样例:
5 1 2 3 4 5
输出样例:
1 1 2 1 2
题解
用s记录 x 中的1的个数,当 x 不为 0 的时候, 循环执行:
- x = x - lowbit(x), s++;
代码
#include <iostream>
using namespace std;
int lowbit(int x)
{
return x&-x;
}
int main(){
int n;
cin >> n;
while(n--)
{
int x, s = 0;
cin >> x;
for(int i = x; i; i -= lowbit(i)) s++;
cout<< s <<" ";
}
}
离散化
区间和
题目
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式 第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式 共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−109 ≤ x ≤ 109,1 ≤ n,m ≤105,
−109 ≤ l ≤ r ≤ 109,
−10000 ≤ c ≤ 10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
思路
-
读入所有操作数据
-
离散化数据。将 n 次操作的坐标,m 次查询的 [l, r] 进行离散化
-
求离散化后数据前缀和
-
将 m 次查询的区间和输出。注意 l 和 r 都需要对应到离散化数据
find 函数的功能:输入映射前的位置 x ,返回映射后的位置+1。(+1的目的是为了求区间和时少一步下表为0的判断)
alls[N] 为什么要开 3e5+10?因为最多可以执行 n 次插入操作,m 次询问操作,而每次询问操作中会包含两个坐标。所以说虽然题目中说数轴是无限长,但离散化的数组只需要开 3e5+10 就够了
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 3e5+10;//n次查询和m次询问相关数据量的上届
int n, m;
int a[N];//存储坐标插入的值
int s[N];//存储a的前缀和
vector<int> alls;//存储(所有与操作有关的)下标
vector<pair<int,int> > add, query;//存储插入和询问操作的数据
int find(int x)
{//返回的是输入的坐标的离散化下标+1
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=1; i<=n; i++)
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);//把x加到待离散化的数组中
}
for(int i=1; i<=m; i++)
{
int l, r;
cin >> l >> r;
query.push_back({l, r});
alls.push_back(l);//把l加到待离散化的数组中
alls.push_back(r);//同
}
//排序+去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//执行前n次的插入操作
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];
//处理后m次询问操作
for(auto item:query)
{
int l = find(item.first);//映射后的位置
int r = find(item.second);
cout << s[r]-s[l-1] << endl;
}
return 0;
}
区间合并
题目
题目描述
给定 n 𝑛 个区间 [li ,ri ],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1 ,3 ] 和 [2 ,6 ] 可以合并为一个区间 [1 ,6 ] 。
输入格式
第一行包含整数n 。接下来n 行,每行包含两个整数l 和r 。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。数据范围
1 ≤ n ≤ 100000,
−109 ≤ li ≤ ri ≤109
样例
输入样例:5
1 2
2 4
5 6
7 8
7 9
输出样例:3
思路提示
可以先按左端点排序,再维护一个区间,与后面一个个区间进行三种情况的比较,存储到数组里去。
C++ 代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std ;
typedef pair<int,int> pii ;
vector<pii> nums,res ;
int main()
{
int st=-2e9,ed=-2e9 ; //ed代表区间结尾,st代表区间开头
int n ;
scanf("%d",&n) ;
while(n--)
{
int l,r ;
scanf("%d%d",&l,&r) ;
nums.push_back({l,r}) ;
}
sort(nums.begin(),nums.end()) ; //按左端点排序
for(auto num:nums)
{
if(ed<num.first) //情况1:两个区间无法合并
{
if(ed!=-2e9) res.push_back({st,ed}) ; //区间1放进res数组
st=num.first,ed=num.second ; //维护区间2
}
//情况2:两个区间可以合并,且区间1不包含区间2,区间2不包含区间1
else if(ed<num.second)
ed=num.second ; //区间合并
}
//(实际上也有情况3:区间1包含区间2,此时不需要任何操作,可以省略)
//注:排过序之后,不可能有区间2包含区间1
res.push_back({st,ed});
//考虑循环结束时的st,ed变量,此时的st,ed变量不需要继续维护,只需要放进res数组即可。
//因为这是最后的一个序列,所以不可能继续进行合并。
/*
for(auto r:res)
printf("%d %d\n",r.first,r.second) ;
puts("") ;
*/
//(把上面的注释去掉,可以在调试时用)
printf("%d",res.size()) ; //输出答案
return 0 ;
}
Java
//单纯的计算区间数
import java.util.Arrays;
import java.util.Scanner;
class Node {
int l;
int r;
public Node(int l, int r) {
this.l = l;
this.r = r;
}
}
public class Main {
static int n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
nodes[i] = new Node(l, r);
}
int res = merge(nodes);
System.out.println(res);
}
private static int merge(Node[] nodes) {
Arrays.sort(nodes, (o1, o2) -> {return o1.l - o2.l;});
int cnt = 0;
int max = Integer.MIN_VALUE;
for (int i = 0; i < n; i ++) {
if (max < nodes[i].l) cnt ++;
max = Math.max(max, nodes[i].r);
}
return cnt;
}
}
// 合并区间
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
class Node{
int l;
int r;
public Node(int l, int r) {
this.l = l;
this.r = r;
}
}
public class Main {
static int n;
static int N = 200010;
static Node[] segs;
static List<Node> res = new ArrayList<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
segs = new Node[n];
for (int i = 0; i < n; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
segs[i] = new Node(l, r);
}
int res = merge(segs);
System.out.println(res);
}
private static int merge(Node[] segs) {
Arrays.sort(segs, (o1, o2) -> {return o1.l - o2.l;});
int start = Integer.MAX_VALUE, end = Integer.MIN_VALUE;
for (int i = 0; i < segs.length; i++) {
int l = segs[i].l;
int r = segs[i].r;
if (end < l) {
if (start != Integer.MAX_VALUE) {
res.add(new Node(start, end));
}
start = l;
end = r;
} else {
end = Math.max(end, r);
}
}
if (start != Integer.MAX_VALUE) {
res.add(new Node(start, end));
}
return res.size();
}
}
// 数组的方式处理
import java.util.Scanner;
import java.util.Arrays;
public class Main{
static int n;
static int N = 200010; //2n
static int[][] segs, res;
static int segSize = 0; //统计segs区间的个数
static int resSize = 0; //统计合并后区间的个数
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
segs = new int[n][2];
res = new int[n][2];
for(int i = 0; i < n; i ++){
segs[i][0] = sc.nextInt();
segs[i][1] = sc.nextInt();
segSize ++;
}
merge(segs);
System.out.println(resSize);
}
public static void merge(int[][] segs){
Arrays.sort(segs, (o1, o2) -> o1[0] - o2[0]);
int start = Integer.MAX_VALUE, end = Integer.MIN_VALUE; // 保证第一次记录值
int j = 0;
for(int i = 0; i < segSize; i ++){
int l = segs[i][0], r = segs[i][1];
if(end < l){
if(start != Integer.MAX_VALUE){
res[j][0] = l;
res[j][1] = r;
j ++;
resSize ++;
}
start = l;
end = r;
}else{
end = Math.max(end, r);
}
}
if(start != Integer.MAX_VALUE){
res[j][0] = start;
res[j][1] = end;
resSize ++;
}
}
}