题解 P1502 【窗口的星星】
IC_QQQ
2019-05-04 16:33:14
## 前置芝士:扫~描~线~
~~这个题其实还有个清流的背景~~
### 尝试解决问题:
我们已知了窗子的长**W**和宽**H**,只需要确定矩形的右上角**M**,整个窗子的位置就确定了。
对于任意的一颗星星 **(x,y)**,我们考虑**M**放在哪些区域可以得到她。
因为星星的坐标是**整数**,我们也令**M**是整数。
注意题目要求:边界不能放。所以,范围就出来了:
左下角$(x,y)$,右上角$(x+W-1,Y+H-1)$。(包括边界)
此时,问题转化成了:**平面上有若干个区域,每个区域都带有一个权值,求在哪个坐标上重叠的区域权值和最大。**
成功转化成了熟悉的**扫~描~线**问题。
### 套路:
取出每个星星形成的矩形,记做**四元组**。
左边:$(x,y,y+H,k)$。
右边:$(x+W,y,y+H,-k)$。
**k**是星星的亮度。
然后以**x**为关键元,对四元组进行排序。
新的问题:**x**相等怎么办?
答案:**右边界**优先。
为什么呢?如果这里同时有一个左边界和一个右边界,右边界已经失效了,要先从扫描线中删除。(前面提到,有效范围是**x~x+W-1**)。
解决了问题,继续进行套路:
对纵坐标进行离散化,得到**tot**个代表数,这条扫描线就被分成了**tot-1**段。
用 **c[i]** 来维护这条扫描线,我们要得到扫描线上的**最大值**。因此,**c[i]** 表示扫描线上第 $i$ 段的权值。
### 走流程:
+ 遇到一条边,用这条边修改扫描线。
+ 扫一遍**c**数组,找到最大值,更新**ans** 。
怎么修改扫描线?我们令纵坐标**y**的代表数为**val(y)**。
修改扫描线:**c[val(y)~val(y+H)-1]**都加上**k**。
因为边界不能放,所以是**val(y+H)-1**。
### 线段树维护**c**数组
每个节点主要是维护最大值**dat**。注意一下下传延迟标记就行了。
### 代码:
```cpp
#include<bits/stdc++.h>
#define R register int
#define ll long long
using namespace std;
const int N=2*1e4+5;
int n,nn,t;
ll H,W,ans;
struct node{
ll x,u,v,w;//u,v纵坐标
}da[N];//四元组
struct aaa{
ll dat,add;//最大值,懒标记
}tree[4*N];
ll row[N],tot;//离散化
ll in(){
ll x=0;char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
bool cmp(node a,node b){//四元组排序
if(a.x==b.x) return a.w<b.w;//重点,右边界优先
return a.x<b.x;
}
void reset(){//预处理,清零
ans=0;tot=0;
memset(da,0,sizeof(da));
memset(row,0,sizeof(row));
memset(tree,0,sizeof(tree));
return;
}
void scan(){//读入
n=in();W=in();H=in();
int a,b,c;nn=n+n;
for(R i=1;i<=n;i++){
a=in();b=in();c=in();
row[i]=b;row[i+n]=b+H;
da[i].x=a;da[i+n].x=a+W;
da[i].u=b;da[i+n].u=b;
da[i].v=b+H;da[i+n].v=b+H;
da[i].w=c;da[i+n].w=-c;
}
return;
}
void row_(){//离散化
sort(row+1,row+1+nn);
tot=unique(row+1,row+1+nn)-(row+1);
return;
}
int query(ll x){//离散化
return lower_bound(row+1,row+1+tot,x)-row;
}
void spread(int pos){//下传懒标记
int ls=pos*2,rs=pos*2+1;
tree[ls].dat+=tree[pos].add;
tree[rs].dat+=tree[pos].add;
tree[ls].add+=tree[pos].add;
tree[rs].add+=tree[pos].add;
tree[pos].add=0;
return;
}
void change(int pos,int pl,int pr,int l,int r,ll w){
if(l<=pl&&r>=pr){
tree[pos].dat+=w;
tree[pos].add+=w;
return;
}
if(tree[pos].add) spread(pos);
int mid=(pl+pr)>>1;
if(l<=mid) change(pos*2,pl,mid,l,r,w);
if(r>mid) change(pos*2+1,mid+1,pr,l,r,w);
tree[pos].dat=max(tree[pos*2].dat,tree[pos*2+1].dat);
return;
}
int main(){
t=in();
while(t--){
reset();scan();row_();
sort(da+1,da+1+nn,cmp);
for(int i=1;i<=nn;i++){
int u=query(da[i].u);
int v=query(da[i].v)-1;
change(1,1,tot-1,u,v,da[i].w);
//懒得建树,将节点的左右端点作为参数传递
ans=max(ans,tree[1].dat);
}
printf("%lld\n",ans);
}
return 0;
}
```