算法基本思想及步骤
离散化:将离散的数据映射到连续的数据中
1、处理输入,用可变数组依次存入离散的数据,再映射到连续的数组中
2、求前缀和,处理询问,求给定区间和
区间合并:分不同情况,只将相交的区间合并
1.存入给定区间,将区间排序
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
思路解析:
n次操作,每次对 x 上的数加 c ,最后询问给定区间和,可知 x 在数轴上是离散的,如果直接开数组求前缀和,计算量过大,所以需要用离散化减小计算量。这里使用了 vector 可变数组,先将每次输入的 x 和 c 都存入一个数组(即代码中的add数组),每次询问的区间 [l, r] 存入一个数组(query数组)。因为离散化需要知道离散化后的数列长度,这取决于前面增加操作时给的坐标和后面询问的区间大小,所以在存储 x、c、l、r 时额外将 x、l 和 r 存入另外的数组(alls数组),对这个数组进行排序和去重的操作,得到的数组长度就是离散化后得到的数列长度,也是求前缀和的长度。然后就可以开始处理增加和查询了,处理增加遍历add数组,将每个 x 当做下标对 a[x] 进行加 c 的操作,对a数组求前缀和,处理查询时遍历query数组,用前缀和的差即可求出区间和(其中找 x、l、r时均是用二分法在alls数组里查找该数,返回alls数组里的下标,即是离散化过后的下标)。
代码
#include
#include
#include
using namespace std;
const int N = 300010; //N是离散化后数组长度,取决于n和m的数量,1<=n,m<=100000,N最大为n+2m=300000
typedef pair<int, int> PII;
vector<int> alls;
vector<PII> add, query;
int m, n;
int a[N], s[N];
//二分求出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 + 1; //因为求前缀和从下标为1开始,所以让得到的所有坐标都向后移动一位
}
int main()
{
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 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]; // 求前缀和
for(auto item : query) // 处理询问
{
int l = find(item.first), 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
思路解析:
两区间只有三种状态,相交、相离和包含,对于多个区间,可将其排序后依次判断。已知经过排序后,每次将后一个区间跟当前区间比较,相交状态时一定是当前区间的后半部分与后一个区间的前半部分相交,相离状态时一定是当前区间在前,后一个区间在后,这时就可以直接将当前区间加入答案,包含状态则只用考虑两个区间的右边界(因为后一个区间的左边界一定是小于等于当前区间左边界的),取最远的那个右边界将其合并即可,因为相交状态也是取最远的右边界取合并区间,所以相交和包含可以合并处理。
代码
#include
#include
#include
using namespace std;
typedef pair<int, int> PII;
void merge(vector<PII> &segs)
{
vector<PII> res;
sort (segs.begin(), segs.end()); //将各区间排序
int st = -2e9, ed = -2e9; //初始边界取无限小
for (auto seg : segs)
{
if (ed < seg.first) //相离状态,当前区间右边界小于下一个区间左边界,直接将当前区间加入答案
{
if (ed != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second; //下一个区间变为当前区间
}
else ed = max(ed, seg.second); //相交或者包含状态,取最大边界,合并区间
}
if (st != -2e9) res.push_back({st, ed}); //存入最后确定的区间
segs = res;
}
int main()
{
int n;
cin >> n;
vector<PII> segs;
for(int i = 0; i < n; i++)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
merge(segs);
cout << segs.size() << endl;
return 0;
}
(模板来源于AcWing算法基础课)