题解 P1502 【窗口的星星】
ysj1173886760 · · 题解
题意大致是给出若干个点以及点的权值,给出一个矩形,求矩形能框住的最大的权值是多少。
我们将双方的角色转换一下,将这些点看做是一个个矩形的左下角的点(x,y),那么(可以框住这个点的矩形)的右上角点坐标的集合(说集合不太严谨)就可以看做是一个矩形,其右上角为 (x+w,y+h)。
那么问题就转化为给出若干个带权值的矩形,求最大的矩形交。(稍微有点不同,这个不是面积)
题中有说窗户的边缘不能算,为了让所有的矩形区间都是闭的,不妨让矩形上下左右都缩小0.5,同时题中有说所有坐标都是整数,那么+-0.5就没有影响了,所以最终得出的矩形就是一个实边的矩形。 左下角坐标为(x,y),右上角坐标为(x+w-1,y+h-1)
注意到因为一般的扫描线算法中线段树里叶子节点i表示线段[i,i+1],也就是一个[l,r] 的线段在线段树中涵盖的区间是[l,r-1]
这道题主要处理的就是相交情况。而且问题中的点全都是离散的,不存在浮点数,所以最后算下来就变成了一个区间加,区间求最值的问题。
还有一点要注意的是在根据横坐标排序的时候,如果横坐标相同也就是线段重合的时候。注意先加上新的再减去旧的,否则重合的情况就没被算到。
最后上代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define mid ((l+r)>>1)
using namespace std;
const int maxn=1e4+10;
int n,tot,w,h;
int yy[maxn<<1]; //离散化的y轴
int rm[maxn<<3],flag[maxn<<3]; //rm表示区间最大值,flag为懒标记 八倍数组,因为2n条线段,线段树要4倍。
struct seg
{
int x,y1,y2,c;
seg(){}
seg(int xx,int yy1,int yy2,int cc){x=xx,y1=yy1,y2=yy2,c=cc;}
}line[maxn<<1];
bool cmp(const seg &a,const seg &b)//这里注意排序,x相同时先处理入边
{
if(a.x==b.x)return a.c>b.c;
return a.x<b.x;
}
void pushdown(int t,int l,int r)//下传懒标记
{
if(flag[t]==0)return;
rm[t]+=flag[t];
if(l!=r) //这里主要防止越界
{
flag[t<<1]+=flag[t];
flag[t<<1|1]+=flag[t];
}
flag[t]=0;
}
void modify(int t,int l,int r,int x,int y,int z)//区间修改
{
if(x<=l&&r<=y)
{
flag[t]+=z;
return;
}
if(x<=mid)modify(t<<1,l,mid,x,y,z);
if(y>mid)modify(t<<1|1,mid+1,r,x,y,z);
pushdown(t<<1,l,mid);
pushdown(t<<1|1,mid+1,r);
rm[t]=max(rm[t<<1],rm[t<<1|1]); //回传最值
}
int main()
{
int t=0;
scanf("%d",&t);
while(t--)
{
scanf("%d %d %d",&n,&w,&h);
memset(rm,0,sizeof(rm));
memset(flag,0,sizeof(flag));
for(int i=1;i<=n;i++)
{
int x=0,y=0,c=0;
scanf("%d %d %d",&x,&y,&c);
line[i]=seg(x,y,y+h-1,c);
line[i+n]=seg(x+w-1,y,y+h-1,-c);
yy[i]=y+h-1;
yy[i+n]=y;
}
n<<=1;//处理的是2n条线段
sort(yy+1,yy+1+n);//排序
tot=unique(yy+1,yy+1+n)-yy-1;//去重
sort(line+1,line+1+n,cmp);//根据横坐标排序
int ans=0;
for(int i=1;i<=n;i++)
{
int l=lower_bound(yy+1,yy+1+tot,line[i].y1)-yy;//二分离散化
int r=lower_bound(yy+1,yy+1+tot,line[i].y2)-yy;
modify(1,1,tot,l,r,line[i].c);//修改,这里注意是l和r,一开始我写的r-1竟然对了9个(但实际上是错误的)
ans=max(ans,rm[1]);//每次更新最值
}
printf("%d\n",ans);
}
return 0;
}
最后告诫自己,具体问题具体分析,本来一开始看到这题直接套了扫描线的板子,后来自己跑了一遍才明白