目录
acwing-802 区间和
离散化算法思想
本题思路
代码如下
代码思路
一些解释
1.pair
2.typedef
3.二分查找
4.for (int i = 1; i <= alls.size(); i++)
在学习离散化之前,看到这道题我直接想到的是前缀和,唯一有点问题的就是题目里面说的是一个无限长的数轴。这点也是我们学习离散化的作用。
其主要思想就是:当数值可取范围特别大(导致我们没办法开数组,会超限),但是我们实际操作的、用上的位置很少、很稀疏,这时候我们就要采用离散化的方法,把使用到的位置的值存放到一个下标从0或者从1开始的数组里面。这样大大节省存储空间,同时也提高了数据处理的效率。
这道题里无限长的数轴就是一个可取的很大的范围。但是我们看实际会使用到的位置是多少?
首先n次插入操作,每次会用到一个坐标,n最大是1e5;下面m次询问,每次询问我们会输入 l 和 r ,这样我们每次要查找的坐标有两个,m的范围是1e5。因此我们实际会用到的坐标只有3e5个。
于是我们创建一个存储离散化之后的数据的数组,在这个数组里进行各种操作。由于我们会使用到前缀和,所以我们这里数据与下标的映射从1开始。
了解了大体思想之后,其实代码还是不太会写,我这里就根据答案的代码进行思路的解释了。感觉还是比较复杂的。
#include
#include
#include
using namespace std;
const int N = 3e5 + 10;
int a[N], s[N];
int n, m;
typedef pair PII;//创建一个新的数据类型
vector alls; // 记录用到的坐标(所以要进行去重和排序)
vector add, query;//分别处理数据的 更改 和 求和
//二分查找x所在的位置(下标)
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x)//找第一个>=x的数的位置
r = mid;
else
l = mid + 1;
}
return r + 1;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
//把坐标x和后来要添加的c作为一个键值对存储(捆绑在一起),此时并没有进行操作,后面for循环才执行了操作
alls.push_back(x);//把坐标存进alls数组
}
for (int i = 0; i < m; i++)
{
int l, r;
cin >> l >> r;
query.push_back({l, r});//把将要计算的区间端点存放在query里,后面for循环时实现其功能
//将用到的坐标存入alls
alls.push_back(l);
alls.push_back(r);
}
//alls去重,去掉重复出现的坐标,并排好序存入alls数组,这样alls数组的元素就是一个 使用到的坐标的连续区间了
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
for (auto item : add) // 实现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];
}
for (auto item : query) // 处理询问
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
我们整体的思路就是:
1.使用alls数组记录下所有提到的、使用到的数轴上的坐标。因此需要对alls数组进行排序和去重,使得alls数组的元素是被压缩后(删掉了未使用的坐标)的连续的坐标序列。
2.我们创建了一个新的类型pair,存储一对键值对,包含两个int类型的相关联的数据。(后面会补充一下这个语法)
3.创建一个元素数据类型为pair的add数组,其作用是通过for循环,将原数组a的数据确定下来(处理+c的工作)
(前面这两个数组实现其功能的前提都是先读取进操作数据[push_back()])
4.得到原数组之后,按照求前缀和的操作,求得s数组。
5.创建一个元素数据类型为pair的query数组,其作用是通过for循环,处理每组询问,以得到所求的区间和再输出。
pair是一种用于存储(相关联)两个值的数据结构,这两个值可以是任何类型,不必相同。它是c++标准模板库中的一个模板类。其使用时需要引头文件 #include
typedef pair PII;
这里我们使用 typedef 关键字,用于为现有的类型定义一个新的名字。这样我们就不用每次创建该类型数据的实际就写 pair
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) // 找第一个>=x的数的位置
r = mid;
else
l = mid + 1;
}
其实这里我们二分实现的功能仅仅是找到一个数,而且alls数组中的坐标都是有序的且并没有重复元素。是比较基础的二分。
我之前写过基础的二分用来查找一个点,而我们后面学习的两种模板的二分适用于比较复杂的需要分开两个满足不同性质的区域。这里我们使用的是 寻找第一个>=x的位置 的模板,使用了更完善的模板,当然能够解决查找一个点这样的基础功能。
add和query的x l r 三个表示坐标的变量,在alls里二分寻找的时候,其实找的是对应元素所在的下标为1到n这连续数的 位置。最后返回目标下标+1,是为了使 a数组和 s数组的下标从1开始,因为前缀和数组需要从1开始。
这是对s数组前缀和的处理。我们这里以alls数组的数量为基础进行求得前缀和,是因为alls数组包含了所有使用到的坐标,并进行了压缩去重,这就保证了我们a数组和s数组最大的使用量也就是alls数组的大小了。
在实际操作中,我们只会处理到alls数组的大小的位置,因为这就是所有可能的坐标值的数量。a和s数组最多位数也就只到alls的最多位,且每位元素与alls的下标一一对应也是连续存储的,但是我们a和s数组定义的大小非常大,预分配了更多的空间以处理可能的最大坐标值,所以我们进行前缀和操作的时候,以alls数组的大小为基准。
5.基础二分
这里附上基础二分的算是模板代码吧。
int find(vector &nums,int target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right) // 循环出口是 查找范围是否为空
{
int mid = left + (right - left) / 2;
if (nums[mid] > target)
{
right = mid - 1;//由于mid值已经判断过不是我们想要的
}
else if (nums[mid] < target)
{
left = mid + 1;
}
else
{
return mid;//一直缩小区间范围,直到mid=target
}
}
return -1;
}
其实看到二分还是有点莫名发怵的[],有点忘记了 。想要复习的可以看之前的文章。
http://blog.csdn.net/Swillow_/article/details/131729869
好啦。关于这道题就解释完啦。有一些新用到的知识,代码还是不太好理解的,相信有了上面的梳理解释,应该可以啦。
如果有问题欢迎指出,非常感谢!!
也欢迎交流建议奥!