窗内的星星不太一样的解法
前言
虽然这个题老师布置在扫描线里面的,看上去也是转化成矩形去求解,但我的第一反应并不是扫描线,并想到了一个个人感觉比较妙的思路。
文章可能比较啰嗦,谅解。
思路
我们可以简化一下题意:用一个长为 \(h\) 宽为 \(w\) 的一个矩形去框一个坐标系里面的一些给出坐标的点,并且每一个点都有一个权值,且点不能再边线上,求能框住的点的权值和的最大值。
我们可以简单举个例子:
假设我们现在要拿高度为2,宽度为4的矩形去框星星。
我们不难想到一种降维的方法,就是可以先对所有点的 \(y\) 坐标进行排序。
我们知道,假设我们定下了矩阵上面宽的高度,那么下面宽肯定是尽可能的向下取,也就可以取到上面宽高度减去限定高度再+1。(显然这种贪心思想是正确的,因为你多取一排肯定比少取一排要好。)
而且,为了避免浪费,我们可以让矩阵高度定在恰好一个点上方一点的位置。(首先,如果你不定在一个点上方一点的位置,那么你显然可以往下再框一行避免浪费,而不是去占一个空行。所以这种贪心思想仍然正确。)
然后用一种双指针的方法,分别指向两个点,来控制两个指针之间的所有点他的高度差在高度限制 以内,符合能被高度极限但宽度无限的矩形包围。
比如说,我们现在 \(y\) 从小到大排序后,从头到尾的点分别是 \(a,b,e,c,d\)。那么我们会先枚举 \(a\) 然后再到 \(b\) 接着到 \(e\)。
但是,当我们枚举到 \(c\) 的时候,我们发现如果 \(c\) 要作为一个矩形的上边宽,那么 \(a,b\) 就不能被框进去,所以我们就要将双指针左边那个指针向后面加,一直加到最后一个能被框进去的点为止。
那么接下来,就是考虑宽度的问题了。
由于我们已经保证当前选进去的这些点,他的高度无论你在确定上边宽,不确定长的条件下怎么框他都能被框进去,那这个问题其实就可以转化为1维的给定数轴上的一些点及其坐标,然后用一个长度为 \(l\) 的区间,最多可以框多少个点了。
首先,我们不难想到一种暴力的思路,就是将这些 \(x\) 坐标再次从小到大排序,并从左到右枚举,二分最右端点(显然越向右越好),然后用前缀和维护这一段的权值和。
但这样时间复杂度为 \(n^2logn\) 肯定死。
那为什么会死呢?
注意,关键点来了
我们发现,由于我们的双指针移动,会删除一些点,并加入一些点,这就会导致答案的改变。而答案改变我们就需要重新算答案,这势必会浪费很多时间。
接着我们又看,我们对于宽度肯定也是框的越多越好,那么我们可以改变一下做题策略。我们可以先将所有的 \(x\) 放在一个数组中并排序。我们知道,最优的左端点之一肯定都在一个 \(x\) 前面,这样能造成更多答案。那么我们就可以二分得到所有的 \(x\) 他自己作为左端点时,右端点前面的第一个点是谁。(我们只需要考虑第一个点,因为这个点和右端点之间肯定不会出现其他点,无论我当前的双指针怎么指。)并且显然有一个结论,就是二分得到的答案他一定满足单调性,因为我们对 \(x\) 排序了,且每次二分的值都在递增。
然后,我们就可以把每个点作为左端点和他能框到的最右端点当作一个区间,并且最优答案肯定出在这些区间。当我们加入和删除一个点的时候,我们就只需要把所有包含了这个点的区间的答案进行修改,而不是把所有的修改。
又由于我们刚刚证明了右端点满足单调性,所有包含一个点的区间,他们肯定构成的又是一个整的区间。(可能有点抽象,需要自己思考一下。)
所以,既然修改一个点,我们修改的是一整个区间,那我们就可以用线段树的区间修改单点查询维护了。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
int x,y,c;
bool operator <(const node &n)const
{
return y<n.y||(y==n.y&&x<n.x);
}
}arr[400005];
int n;
int ls[400005],lslen;
int nxt[400005];
int down[400005],up[400005];
struct segmentree
{
int l,r;
long long maxx,tag;
}tree[2000005];
void build(int p,int l,int r)
{
tree[p].l=l,tree[p].r=r;
tree[p].maxx=tree[p].tag=0;
if(l==r) return;
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void update(int p)
{
tree[p].maxx=max(tree[p<<1].maxx,tree[p<<1|1].maxx);
}
void pushdown(int p)
{
if(tree[p].tag)
{
int tag=tree[p].tag,l=p<<1,r=p<<1|1;
tree[l].tag+=tag,tree[r].tag+=tag;
tree[l].maxx+=tag,tree[r].maxx+=tag;
tree[p].tag=0;
}
}
void change(int p,int l,int r,int x)
{
if(tree[p].l>r||tree[p].r<l) return;
if(tree[p].l>=l&&tree[p].r<=r)
{
tree[p].maxx+=1ll*x;
tree[p].tag+=x;
return;
}
pushdown(p);
change(p<<1,l,r,x);
change(p<<1|1,l,r,x);
update(p);
}
int w,l;
int main()
{
while(~scanf("%d%d%d",&n,&w,&l))
{
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&arr[i].x,&arr[i].y,&arr[i].c);
ls[i]=arr[i].x;
}
sort(arr+1,arr+n+1);
sort(ls+1,ls+n+1);
lslen=unique(ls+1,ls+n+1)-ls-1;
build(1,1,lslen);
for(int i=1;i<=n;++i) arr[i].x=lower_bound(ls+1,ls+lslen+1,arr[i].x)-ls;
for(int i=1;i<=lslen;++i)
{
nxt[i]=lower_bound(ls+1,ls+lslen+1,ls[i]+w)-ls-1;
}
int last=1;
for(int i=1;i<=n;++i)
{
int l=lower_bound(nxt+1,nxt+lslen+1,arr[i].x)-nxt;
down[i]=l;//up down分别处理包含一个点的区间所构成的区间左右端点。
up[i]=lslen;
//cout<<i<<" "<<down[i]<<" "<<up[i]<<endl;
}
long long ans=0;
for(int i=1;i<=n;++i)
{
while(arr[i].y-arr[last].y>=l&&last<=n)
{
change(1,down[last],up[last],-1*arr[last].c);
last++;
}
change(1,down[i],up[i],arr[i].c);
ans=max(ans,tree[1].maxx);
}
cout<<ans<<endl;
}
}