洛谷题单指南-前缀和差分与离散化-P1496 火烧赤壁
原题链接:https://www.luogu.com.cn/problem/P1496
题意解读:给定n个区间[a,b),计算所有区间覆盖的总长度。
解题思路:
方法1、离散化
先思考一种比较直观的思路:
既然要计算多个区间覆盖的总长度,可以枚举每一个区间[a,b),通过一个桶数组来标记区间中所有的点f[x] = 1,最终统计所有为1的点数量,即可得到总长度。
但是,要注意此题区间[a,b)中a,b的取值范围−2^31≤𝑎<𝑏<2^31,无法通过桶数组来标记,会导致内存超限。
怎么解决呢?这里就要借助离散化的方法!
所谓离散化,就是用数字的相对值替代他们的绝对值,能够将分布广而稀疏的数字转为连续的、密集的分布,从而起到节省空间的效果。
对于本问题,尽管a,b取值范围较大,但是区间不超过20000个,也就是a,b的值最多40000个,我们可以建立一个1~40000的数值与真实a、b之间的映射关系,如样例:建立一个c[]数组,编号与a,b数值对应关系如下
c[] | 1 | 2 | 3 | 4 | 5 | 6 |
a,b数值 | -1 | 1 | 2 | 5 | 9 | 11 |
我们通过桶数组标记所有[-1,1),[5,11),[2,9)的值对应的编号为1
[-1,1):对应编号[1,2),f[1] = 1
[5,11):对应编号[4,6),f[4] = f[5] = 1
[2,9):对应编号[3,5),f[3] = f[4] = 1
综上:
f[] | 1 | 2 | 3 | 4 | 5 | 6 |
值 | 1 | 0 | 1 | 1 | 1 | 0 |
再通过枚举f[]数组计算覆盖的总长度,对于每一个f[i] = 1,其所表示在真实区间上的数值覆盖长度是开始c[i+1]-c[i]
那么,如何计算c[]数组,也就是如何建立离散化之后的有序数字与原始a,b数值之间的映射关系?
可以如下处理:
第一步、将a[]、b[]所有的数字保存到新的数组c[]
第二步、对c[]进行排序、去重
第三步、对a[]、b[]中每一个数字,通过二分在c[]中查找第一出现的下标,并用下标更新a[],b[]中原来的数字
即对a[],b[]中每一个数字完成了离散化处理。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 20005;
int n;
int a[N], b[N], c[2 * N], cnt;
int f[2 * N];
long long ans;
int bs(int x)
{
int l = 1, r = cnt, res = -1;
while(l <= r)
{
int mid = l + r >> 1;
if(c[mid] >= x) res = mid, r = mid - 1;
else l = mid + 1;
}
return res;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i];
c[++cnt] = a[i];
c[++cnt] = b[i];
}
//排序
sort(c + 1, c + cnt + 1);
//去重
int j = 0;
for(int i = 1; i <= cnt; i++)
if(i == 1 || c[i] != c[i - 1])
c[++j] = c[i];
cnt = j;
//将a,b离散化
for(int i = 1; i <= n; i++)
{
int x = bs(a[i]);
int y = bs(b[i]);
//标记着火的点
for(int j = x; j < y; j++)
f[j] = 1;
}
//枚举着火点,计算对应真实位置的长度
for(int i = 1; i <= cnt; i++)
{
if(f[i])
{
ans += c[i + 1] - c[i];
}
}
cout << ans;
return 0;
}
方法2、区间合并
参考:https://www.cnblogs.com/jcwy/p/18208437 里介绍的区间合并方法。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 20005;
struct node
{
int a, b;
bool operator < (const node &x) const
{
return a < x.a;
}
};
node range[N];
vector<node> newrange;
int n, ans;
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> range[i].a >> range[i].b;
}
//将区间按左端点从小到大排序
sort(range + 1, range + n + 1);
//以下进行区间合并
node cur = range[1]; //初始区间为第一个区间
for(int i = 2; i <= n; i++)
{
if(range[i].a > cur.b)
{
newrange.push_back(cur);
cur = range[i];
}
else
{
cur.b = max(cur.b, range[i].b);
}
}
newrange.push_back(cur);
//计算区间覆盖长度
for(auto i : newrange)
{
ans += i.b - i.a;
}
cout << ans;
return 0;
}