P2061 [USACO07OPEN]City Horizon S(区间问题,线段树 / 堆)

题目描述

Farmer John has taken his cows on a trip to the city! As the sun sets, the cows gaze at the city horizon and observe the beautiful silhouettes formed by the rectangular buildings.
The entire horizon is represented by a number line with N (1 ≤ N ≤ 40,000) buildings. Building i’s silhouette has a base that spans locations Ai through Bi along the horizon (1 ≤ Ai < Bi ≤ 1,000,000,000) and has height Hi (1 ≤ Hi ≤ 1,000,000,000). Determine the area, in square units, of the aggregate silhouette formed by all N buildings.
有一个数列,初始值均为0,他进行N次操作,每次将数列[ai,bi)这个区间中所有比Hi小的数改为Hi,他想知道N次操作后数列中所有元素的和。

输入格式

第一行一个整数N,然后有N行,每行三个正整数ai、bi、Hi。

输出格式

一个数,数列中所有元素的和。

样例

输入
4
2 5 1
9 10 4
6 8 2
4 6 3
输出
16

说明/提示

N<=40000 , a、b、k<=10^9 。

线段树

题目分析

首先因为区间范围比较大,我们要对区间进行离散化,但是将所有区间点进行离散化之后,直接建立线段树就会丢失部分的区间线段树同层的两个节点之间的距离不是1,因为离散化后每个节点之间的距离不为1)。

因此我们要稍微调整一下线段树的写法:一段区间 [l,r] 的左儿子和右儿子代表的区间分别为 [l,mid] 和 [mid,r]。 这样才能保证线段树能覆盖完整的区间。

我们可以对所有的区间操作按h进行升序排序,这样就能保证在覆盖第i个区间时,该区间的h为此时的最大值。即:对操作进行排序后,我们可以不用考虑之前操作的影响,直接对该区间进行覆盖

这样,我们要维护的线段树需要的操作为:
1、将 [l,r] 区间中的点全部覆盖为x。
2、查询整颗线段树的权值和。

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=3e5+5,mod=998244353;
struct Oper{					//记录操作的结构体
	int a,b,h;
	bool operator< (const Oper& x)const
	{ return h<x.h; }
}q[N];
struct Node{					//线段树结构体
	int l,r;
	LL cha;						//cha!=0时表示该节点段的所有点的值全部为cha(懒标记)
}tr[N*4];
vector<int> w;					//离散化数组
void pushdown(int u)			//懒标记下方
{
	if(tr[u].cha)
	{
		tr[u<<1].cha=tr[u].cha;
		tr[u<<1|1].cha=tr[u].cha;
		tr[u].cha=0;
	}
}
void build(int u,int l,int r)		//建树
{
	tr[u]={l,r,0};
	if(l==r-1) return ;
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid,r);		//注意子区间为[l,mid]和[mid,r]
}
void modify(int u,int l,int r,LL x)				//将[l,r]区间全部改为x
{
	if(tr[u].r<l||r<tr[u].l) return;
	if(l<=tr[u].l&&tr[u].r<=r) tr[u].cha=x;
	else {
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(mid>l) modify(u<<1,l,r,x);
		if(mid<r) modify(u<<1|1,l,r,x);
	}
}
LL sum(int u)			//求总的权值和
{	//发现存在懒标记时,说明该段全为cha,直接返回即可
	if(tr[u].l==tr[u].r||tr[u].cha) return tr[u].cha*(w[tr[u].r]-w[tr[u].l]);
	return sum(u<<1)+sum(u<<1|1);
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int a,b,h;
		cin>>a>>b>>h;
		q[i]={a,b,h};
		w.push_back(a);
		w.push_back(b);
	}
	sort(w.begin(),w.end());					//离散化区间点
	w.erase(unique(w.begin(),w.end()),w.end());
	build(1,0,w.size()-1);						//建树
	for(int i=1;i<=n;i++)						//求离散化后的区间点值
	{
		q[i].a=lower_bound(w.begin(),w.end(),q[i].a)-w.begin();
		q[i].b=lower_bound(w.begin(),w.end(),q[i].b)-w.begin();
	}
	sort(q+1,q+1+n);					//对操作的h进行排序
	for(int i=1;i<=n;i++) modify(1,q[i].a,q[i].b,q[i].h);
	cout<<sum(1)<<endl;					//输出答案
	return 0; 
}

题目分析

这道题也可以用 扫描线 做,我们可以将所有区间按左端点进行排序。
然后从小到大枚举每一小段区间。
如果这段小区间包含一个新的操作区间,就将其放入堆中。
请添加图片描述
然后累加出每个小区间的贡献即可:一段小区间的贡献=小区间的长度*堆顶的权值

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=1e5+5,mod=998244353;
struct Quer{
	int a,b,h;
	bool operator<(const Quer& x)const			//堆内要用h作为关键字
	{ return h<x.h; }
}q[N];
bool cmp(Quer x,Quer y)
{ return x.a<y.a; }
priority_queue<Quer> heap;
int pos[N];
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)			//输入数据
	{
		int a,b,h;
		cin>>a>>b>>h;
		q[i]={a,b,h};
		pos[i*2]=b;					//将区间点存入pos[]
		pos[i*2-1]=a;
	}
	sort(q+1,q+1+n,cmp);			//将操作按左端点进行排序
	sort(pos+1,pos+1+2*n);
	LL ans=0;
	int cnt=0;						//记录放了多少个区间
	for(int i=1;i<2*n;i++)
	{
		while(heap.size()&&heap.top().b<=pos[i]) heap.pop();	//先让所有右端点在当前位置的左边的堆顶点弹出
		while(pos[i]==q[cnt+1].a) heap.push(q[++cnt]);			//将所有左端点在当前位置的点放入堆
		if(heap.size()) ans+=(LL)heap.top().h*(pos[i+1]-pos[i]);//更新[ pos[i], pos[i+1] ]区间的答案
	}
	cout<<ans<<endl;
	return 0; 
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值