位运算-离散化-区间合并

位运算

二进制中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),那么:

  1. 对于x的二进制表示中,k位之右的数,它们在x中都对应了0,所以对于这部分的数值,x & (-x) = 0。
  2. 对于x的二进制表示中,k之左的数值,它们在x中对应了0 或者 1,而在-x的二进制表示中,他们对应 1 或 0, 与 x 的数值相反(0对1, 1对0)。因此,在进行按位与运算时,x中第k位之左的数值能够与-x中的相应位数值得到0。
  3. 对于第 k 位来说,x 的第k位为1,-x的第k为也为1,因此,在进行按位与运算时,x中第k位的数值能够与-x中的第k为数值得到1。
  4. 因此 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

思路

  1. 读入所有操作数据

  2. 离散化数据。将 n 次操作的坐标,m 次查询的 [l, r] 进行离散化

  3. 求离散化后数据前缀和

  4. 将 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 ++;
        } 
    }
}
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值