P2061 [USACO07OPEN]City Horizon S(区间问题,线段树 / 堆)
Posted li_wen_zhuo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}
以上是关于P2061 [USACO07OPEN]City Horizon S(区间问题,线段树 / 堆)的主要内容,如果未能解决你的问题,请参考以下文章
bzoj1645 / P2061 [USACO07OPEN]城市的地平线City Horizon(扫描线)
BZOJ_1654_[Usaco2007 Open]City Horizon 城市地平线_扫描线