【JZOJ3965】【Usaco2014 March Gold】The Lazy Cow(扫描线+线段树)

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);
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值