算法刷题总结—位运算、离散化、区间合并

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即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值