第一章 算法基础 整数离散化

1、算法思路

这里我们分析的是一种整数保序的离散化。所谓离散化,是针对这样一种情形。当我们要处理的数组元素的数量极大(有些题目干脆声明为无限大),而我们用到的元素个数很少(例如 1 0 5 10^5 105),我们就可以把这些用到的数映射成一个元素较少的数组。也就是将所有用到的数组序号转换成连续的自然数。所谓保序,就是这个元素较少的数组里面元素的先后顺序与原数组是相同的,也就是转换的时候,小的序号对应新数组小的序号,如下例的12312312312312312123123123对应0,12312312312312312123123124对应1一样。
例如:
在这里插入图片描述
假设我们只用到 12312312312312312123123123 12312312312312312123123124 两位
我们需要将数组映射为一个足够使用的数组
在这里插入图片描述
以后对那两个数的对应元素的操作就等价于在新数组中对0和1对应元素的操作。
明确之后,我们有一下两个问题需要处理:

  1. 一般题目中需要操作的元素未必是唯一的。也就是说可能会重复操作某些位置,所以应该将所有可能要操作的元素序号进行去重操作
  2. 如何快速找出一个原数组序号x对应的新数组下标

对于问题1,我们做如下处理:

vector<int>alls;//存储所有需要离散化的值,也就是所有我们可能要用到的下标
sort(alls.begin(),alls.size());//将所有值排序
alls.erase(unique(alls.begin(),alls.end()),alls.end());//去除重复元素

unique函数会去重,并把不重复的数组放到前面,把重复的数组放后面,返回不重复数组的最后一位,erase把他们删掉
对于问题2,我们使用二分法进行处理:

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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;
}

2、例题

区间和

假定有一个无限长的数轴,数轴上每个坐标上的数都是 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<vector>
#include<algorithm>
#include<cstring>
using namespace std;
//因为有x、l、r三种操作数,所以数组要开三倍的10^5
const int N = 3e5 + 10;
typedef pair<int,int> PII;
//a用来存储元素的值,sum用来存储前缀和
int a[N],sum[N];
//因为我们需要对下标进行映射之后才有元素真正的坐标,所以需要将之前进行的操作先存起来,之后再重新进行操作。
vector<PII> add,query;
vector<int> alls;
//二分找大于等于x的第一个数
int findx(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()
{
	int n,m;
	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 < m; i ++)
	{
		int l,r;
		cin >> l >> r;
		query.push_back({l,r});
		alls.push_back(l);
		alls.push_back(r);
	}
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	for(auto u : add)
	{
		a[findx(u.first)] += u.second;
	}
	for(int i  = 1; i <= alls.size(); i ++)
	{
	    sum[i] = sum[i - 1] + a[i];
	} 
	for(auto u : query)
	{
		int l = findx(u.first);
		int r = findx(u.second);
		cout << sum[r] - sum[l - 1] << endl;
	}
}	

参考资料

Acwing

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值