bzoj 2388 旅行规划 分块+二分+凸包

26 篇文章 0 订阅
1 篇文章 0 订阅

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2388

题解

一道貌似很恶心的分块题,调了好久。。
首先需要维护动态前缀和,对于一个点s,l< s< r,那么s需要增加的值为(s-l+1)*c,如果s位于首尾块内,那么就可以直接暴力修改,如果s位于中间的块内,那么对于每个块要记录出该块首项需要加的值,以及该块中各个点的公差。对于在r点右边的块,就可以直接打标记记录该块的每个点需要增加的值。这需要用三个数组来记录。

查询时,与l,r位于相同块的点直接暴力查询即可,位于l,r中间的块的点,需要在每个块内维护一个凸包,可以发现,如果斜率是凹的点一定不会是最优,这样就可以对于每一个块内进行二分查找。

code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
int n,m,q,block;
ll inf=1ll<<60;
ll a[N],sum[N];
ll dif[N];//记录每个块的首项需要加的值
ll fir[N];//记录每个块内的公差
ll add[N];
int pos[N],st[N],num[N],con[510][510];

double slop(int x,int y)
{
    return (sum[x]-sum[y])/(x-y);
}

void reset(int x)
{
    int l=(x-1)*block+1,r=min(n,x*block),top=0;
    st[++top]=l;
    for(int i=l+1;i<=r;i++)
    {
        while(top>=2&&slop(st[top-1],st[top])<slop(st[top-1],i))top--;
        //维护块内凸包,使得满足答案单调性
        st[++top]=i;
    }
    st[0]=0,st[top+1]=n+1,num[x]=top;
    for(int i=0;i<=top+1;i++)con[x][i]=st[i];
    //记录凸包的点
}

void PushDown(int x)
{
    ll tmp=dif[x];
    for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
        tmp+=fir[x],sum[i]+=tmp+add[x];
    dif[x]=fir[x]=add[x]=0;
    //对于一个需要用到的块暴力重构
}

void updata(int l,int r,ll c)
{
    ll tmp=0;
    if(pos[l]==pos[r])
    {
        PushDown(pos[l]);
        for(int i=l;i<=r;i++)tmp+=c,sum[i]+=tmp;//暴力修改
        for(int i=r+1;i<=min(pos[l]*block,n);i++)sum[i]+=tmp;//暴力修改
        for(int i=pos[l]+1;i<=m;i++)add[i]+=tmp;//打标记
        reset(pos[l]);
        return ;
    }
    PushDown(pos[l]);//重构
    for(int i=l;i<=pos[l]*block;i++)tmp+=c,sum[i]+=tmp;//暴力修改
    reset(pos[l]);
    for(int i=pos[l]+1;i<pos[r];i++)dif[i]+=tmp,fir[i]+=c,tmp+=c*(ll)block;//打标记
    PushDown(pos[r]);
    for(int i=(pos[r]-1)*block+1;i<=r;i++)tmp+=c,sum[i]+=tmp;
    for(int i=r+1;i<=min(n,pos[r]*block);i++)sum[i]+=tmp;
    reset(pos[r]);
    for(int i=pos[r]+1;i<=m;i++)add[i]+=(ll)(r-l+1)*c;
}

ll calc(int x)
{
    return sum[x]+dif[pos[x]]+fir[pos[x]]*(ll)(x-(pos[x]-1)*block)+add[pos[x]];
    //计算某一点的具体值
}

ll find(int x)
{
    int l=1,r=num[x];
    while(l<=r)
    {
        int mid=l+r>>1;
        ll t1=calc(con[x][mid-1]),t2=calc(con[x][mid]),t3=calc(con[x][mid+1]);
        if(t1<t2&&t2<t3)l=mid+1;
        else if(t1>t2&&t2>t3)r=mid-1;
        else return t2;
    }
    //二分查找块内最值
}

ll query(int l,int r)
{
    ll ans=-inf;
    if(pos[l]==pos[r])
    {
        for(int i=l;i<=r;i++)ans=max(ans,calc(i));
        return ans;
    }
    for(int i=l;i<=pos[l]*block;i++)ans=max(ans,calc(i));
    for(int i=(pos[r]-1)*block+1;i<=r;i++)ans=max(ans,calc(i));
    for(int i=pos[l]+1;i<pos[r];i++)ans=max(ans,find(i));
    return ans;
}

int main()
{
    cin>>n;
    block=(int)sqrt(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
        pos[i]=(i-1)/block+1;
    }
    sum[0]=sum[n+1]=-inf;
    if(n%block)m=n/block+1;
    else m=n/block;
    for(int i=1;i<=m;i++)reset(i);
    cin>>q;
    for(int i=1;i<=q;i++)
    {
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);
        if(!op)
        {
            ll z;scanf("%lld",&z);
            updata(x,y,z);
        }else
            printf("%lld\n",query(x,y));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值