【题解】洛谷P1496 火烧赤壁 (离散化)
我们首先先看数据,n<=20000,数据不多,但是范围大(-10^9<=Ai,Bi<=10^9),这时,就可以用离散化了。但是在这里我们会遇到区间重合的问题(也可以使用区间合并),如下图
本题的题意是让我们求出燃烧位置的长度之和。区间重合时只需要区间最大数减去最小数字,那么我们如何获得这个数字呢?
朴素的想法是,在面对一段连续的区间时,从边界一直枚举下去到尽头用if语句终止。当我们离散化后,也不会有数组长度的问题了。对于连续区间,我们会考虑差分和前缀和。但是,如果如何区分左边界和右边界呢?
我的想法是,我们只需要[l+1,r]之间的区间进行操作,这样求前缀和后,sum[l]就是0,其他都大于0。
直接看代码吧qwq
/* P1496 火烧赤壁
* 作者: Yukie
* 日期: 2023-12-24
* 算法: 离散化(map方法)
*/
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int maxn = 2e4 + 5;
vector<PII> fire;
map<int, int> h;
int n, Map[2 * maxn], sum[2 * maxn];
LL ans;
int find(int value) {
for (map<int, int>::iterator it = h.begin(); it != h.end(); it++) {
if (it->second == value)
return it->first;
}
} //由离散化后的结果找原来的数,因为key本身就唯一,映射后也是从1升序,所以key和value均唯一
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int l, r;
cin >> l >> r;
fire.push_back({l, r});
h[l] = 1, h[r] = 1;
}
//h[x]就是离散化后的结果,已排好序
int idx = 1;
for (auto &t : h) { // 使用auto修改map值时,需要添加引用符号&
t.second = idx;
idx++;
}
for (auto fi : fire) {
int x = h[fi.first], y = h[fi.second];
Map[x + 1]++, Map[y + 1]--; //差分,为什么是左边界+1呢?为了到时候查找时能区分出来左右,实际上问题转为[l+1,r]
}
for (int i = 1; i <= h.size(); i++)
sum[i] += sum[i - 1] + Map[i]; //前缀和
int l, r; //不为0的区间的左端点和右端点。
for (int i = 1; i <= h.size(); i++) {
if (sum[i] != 0 && sum[i - 1] == 0) //查找连续着火区间的左边界
l = i - 1;
if (sum[i] != 0 && sum[i + 1] == 0) {
r = i; //查找连续着火区间的右边界
ans += find(r) - find(l);
//由于这是离散化后的区间长度,并不是真正的区间长度,所以我们要找到原来的l与r,将他们相减,就是原来的区间长度。
}
}
cout << ans << endl;
return 0;
}
样例解释:
输入区间排序后:-1 1 2 5 9 11
映射后:1 2 3 4 5 6
差分数组:0 1 -1 1 1 -1
前缀和数组:0 1 0 1 2 1
不用map的离散化版本:
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2e4+5;
int n;
int b[N*2];
int map[N*2],sum[N*2];
struct items{
int l;//左。
int nl;//l离散化后的位置。
int r;//右。
int nr;//r离散化后的位置。
}len[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>len[i].l>>len[i].r;//读入每个区间的l,r。
b[i]=len[i].l,b[n+i]=len[i].r;//读入一个数组进行离散化操作。
}
sort(b+1,b+1+2*n);//由于unique需要在一个有序的数组下进行去重,所以我们需要排序。
auto k=unique(b+1,b+1+2*n);//去重操作。
for(int i=1;i<=n;i++){
len[i].nl=lower_bound(b+1, k, len[i].l)-b;//通过lower_bound来查询每个l离散化后的位置,并存储在len[i].nl。
len[i].nr=lower_bound(b+1, k, len[i].r)-b;//通过lower_bound来查询每个r离散化后的位置,并存储在len[i].nr。
}
for(int i=1;i<=n;i++)map[len[i].nl+1]++,map[len[i].nr+1]--;//差分。
for(int i=1;i<=(k-b-1);i++)sum[i]+=sum[i-1]+map[i];//(k-b-1)为元素个数。
int l,r;//不为0的区间的左端点和右端点。
long long ans=0;
for(int i=1;i<=(k-b-1);i++){
if(sum[i]!=0&&sum[i-1]==0)l=i-1;
if(sum[i]!=0&&sum[i+1]==0){
r=i;
ans+=b[r]-b[l];//由于这是离散化后的区间长度,并不是真正的区间长度,所以我们要找到原来的l与r,将他们相减,就是原来的区间长度。
}
}
cout<<ans<<endl;
return 0;
}
书上的题解版本:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
#define MAXN 20010
int n,a[MAXN],b[MAXN],f[MAXN*2],dtop,ctop,d[MAXN*2];
LL c[MAXN*2];
LL ans;
//dtop和ctop分别用来记录原数组的元素数量和排序去重后的元素数量。
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d%d",&a[i],&b[i]); //输入坐标
d[++dtop] = a[i]; //d数组记录所有坐标
d[++dtop] = b[i];
}
sort(d+1,d+dtop+1); //排序
for(int i=1;i<=dtop;i++)
if (d[i] != d[i-1] || i == 1) c[++ctop] = d[i]; //拷贝去重
for(int i=1;i<=n;++i) {
int x = lower_bound(c+1,c+ctop+1,a[i])-c; //离散化
int y = lower_bound(c+1,c+ctop+1,b[i])-c;
for (int j=x;j<y;j++) f[j] = 1; //f[i]表示区间[c[i],c[i+1]]是否被染色
}
for(int i=1;i<ctop;i++)
if(f[i]) ans += c[i+1]-c[i];
printf("%lld",ans);
return 0;
}
样例解释:
3 -1 1 5 11 2 9
[-1,1],[2,11]区间分别被染色,[1,2],11后面都没有被染色
对应:f[6] = {1,0,1,1,1,0}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!