Problem
田里有N(1<= N <=100,000)片青草。第i 片有gi 单位的青草(1<=gi<=10,000),并且它唯一地位于田里坐标为(xi,yi)(0<= xi, yi<=1,000,000)的点。须定一个点(可不为整点)为起点,使得从起点出发走K(1<= K<= 2,000,000)步(只能向东南西北走,且可不走整数步,但合起来≤K步)的距离以内有尽可能最多的青草。求能够到的最大青草量。
Solution
这题很水,但我比赛的时候没有想到那显而易见的正解,且由于被T2的链剖坑去大多时间,这题竟连暴力也没有打。
这题很水,只需要一个扫描线即可。
首先,我们分析题意,发现是给我们一堆有权值的点,然后选个起点,使所有与起点曼哈顿距离≤K的点的权值和尽量大。那么曼哈顿距离,实际上也即以起点为中心的一个90°角的菱形。譬如若起点为(1,1),且K为2,则那个菱形如下图蓝色方框所示:
那么按照套路,碰到菱形,当然要把它转化成正方形。怎么转化呢?颇为简单,只需要将原来的(x,y)变为(x-y,x+y)即可。(别问我怎么证明)例如上图的菱形就会被转化为下图的绿色方框:
那么既然是要求让一个半边长为K的正方形覆盖尽量多的权值,我们也可以将那些有权值的点各视为一个半边长为K的正方形,然后覆盖重叠最多的部分,我们就可以以其中任意一点为起点。
这样题目就被转化为了求一堆正方形覆盖重叠最大的权值和。我比赛时是想着用二维线段树的,但估计空间不够。
其实我们可以直接上扫描线。存储每个正方形的上边和下边,上边为正权,下边为负权。每扫到一条边,我们就在一棵一维线段树上相应的位置区间加上那条边对应的权值,并计算全树最大值。
由于图形被转换了,所以坐标可能会出现负数,那么按照常规套路,我们就给线段树的区间编号都加上一个大数即可。
这题很水,真的很水。
时间复杂度:
O(nlog2S)
O
(
n
l
o
g
2
S
)
(S为坐标范围)。
Code
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 200001
#define M 3000000
#define S 8*M+10
#define A v*2
#define B A+1
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n,k,x,y,f[S],tag[S],ans;
struct grass
{
int g,x,y;
}a[N];
inline bool compare(grass a,grass b)
{
return a.y>b.y||a.y==b.y&&a.g>b.g;
}
inline void push(int v)
{
int val=tag[v];
if(!val)return;
f[A]+=val;tag[A]+=val;
f[B]+=val;tag[B]+=val;
tag[v]=0;
}
inline void update(int v)
{
f[v]=max(f[A],f[B]);
}
void modify(int v,int l,int r,int x,int y,int val)
{
if(x<=l&&r<=y)
{
f[v]+=val;
tag[v]+=val;
return;
}
push(v);
int mid=(l+r)/2;
if(x<=mid)modify(A,l,mid,x,y,val);
if(y>mid)modify(B,mid+1,r,x,y,val);
update(v);
}
int main()
{
scanf("%d%d",&n,&k);
n*=2;
fo(i,1,n)
{
scanf("%d%d%d",&a[i].g,&x,&y);
a[i+1].g=-a[i].g;
a[i].x=a[i+1].x=x-y;
a[i].y=x+y+k;
a[++i].y=x+y-k;
}
sort(a+1,a+n+1,compare);
fo(i,1,n)
{
x=a[i].x;
modify(1,0,2*M,x-k+M,x+k+M,a[i].g);
ans=max(ans,f[1]);
}
printf("%d",ans);
}