bzoj2388: 旅行规划【分块+凸包】

Description

OIVillage是一个风景秀美的乡村,为了更好的利用当地的旅游资源,吸引游客,推动经济发展,xkszltl决定修建了一条铁路将当地n个最著名的经典连接起来,让游客可以通过火车从铁路起点(1号景点)出发,依次游览每个景区。为了更好的评价这条铁路,xkszltl为每一个景区都哦赋予了一个美观度,而一条旅行路径的价值就是它所经过的景区的美观度之和。不过,随着天气与季节的变化,某些景点的美观度也会发生变化。
xkszltl希望为每位旅客提供最佳的旅行指导,但是由于游客的时间有限,不一定能游览全部景区,然而他们也不希望旅途过于短暂,所以每个游客都希望能在某一个区间内的车站结束旅程,而xkszltl的任务就是为他们选择一个终点使得旅行线路的价值最大。可是当地的景点与前来观光的旅客实在是太多了,xkszltl无法及时完成任务,于是找到了准备虐杀NOI2011的你,希望你能帮助他完成这个艰巨的任务。

Input

第一行给出一个整数n,接下来一行给出n的景区的初始美观度。
第三行给出一个整数m,接下来m行每行为一条指令:
1. 0 x y k:表示将x到y这段铁路边上的景区的美观度加上k;
2. 1 x y:表示有一名旅客想要在x到y这段(含x与y)中的某一站下车,你需要告诉他最大的旅行价值。

Output

对于每个询问,输出一个整数表示最大的旅行价值。

Sample Input

5

1 8 -8 3 -7

3

1 1 5

0 1 3 6

1 2 4

Sample Output

9

22

HINT

Data Limit:

对于20%的数据,n,m≤3000;

对于40%的数据,n,m≤30000;

对于50%的数据,n,m≤50000;

另外20%的数据,n,m≤100000,修改操作≤20;

对于100%的数据,n,m≤100000。

解题思路:

相当于要支持区间加法,动态维护前缀和。
如果我们把下标看做横坐标,前缀和看做纵坐标,那答案肯定是在凸包的最高点上,所以关键在于动态维护凸包。

本来先用线段树,但修改时无法快速合并,所以还是上分块大法。

分块维护块内凸包,注意到一次修改增加k,如果覆盖了整块,块内相邻两点的斜率都会增加k,所以凸包上的点是不会变的,而r之后的块都加了固定的值,只有询问边缘两块会受影响,暴力重构即可。所以每块要打三个标记,斜率变化的首项fir,斜率变化的公差d,整块加的值add,重构时pushdown即可。

每次询问二分整块的凸包,直接计算边缘的点值。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=100005,M=350;
const ll INF=1e18;
int n,m,cnt,S;
int belong[N],st[M],ed[M],num[M];
int stk[M],top,p[M][M];
ll a[N],fir[M],d[M],add[M];

inline double slope(int x,int y){return (double)(a[y]-a[x])/(y-x);}

inline ll calc(int x)
{
    if(x==0||x==n+1)return -INF;
    int t=belong[x];
    return a[x]+fir[t]+d[t]*(x-st[t])+add[t];
}

void build(int x)
{
    stk[top=1]=st[x];
    for(int i=st[x]+1;i<=ed[x];i++)
    {
        while(top>=2&&slope(stk[top-1],stk[top])<slope(stk[top-1],i))top--;
        stk[++top]=i;
    }
    stk[0]=0,stk[top+1]=n+1;
    num[x]=top;
    for(int i=0;i<=top+1;i++)p[x][i]=stk[i];
}

void pushdown(int x)
{
    ll tmp=fir[x];
    for(int i=st[x];i<=ed[x];i++)
        a[i]+=tmp,tmp+=d[x],a[i]+=add[x];
    fir[x]=d[x]=add[x]=0;
}

ll query(int x)
{
    int l=1,r=num[x];
    while(l<=r)
    {
        int mid=l+r>>1;
        ll t1=calc(p[x][mid-1]),t2=calc(p[x][mid]),t3=calc(p[x][mid+1]);
        if(t1<t2&&t2<t3)l=mid+1;
        else if(t1>t2&&t2>t3)r=mid-1;
        else return t2;
    }
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=getint();
    for(int i=1;i<=n;i++)a[i]=getint()+a[i-1];
    a[0]=a[n+1]=-INF;
    S=sqrt(n),cnt=(n%S?n/S+1:n/S);
    for(int i=1;i<=n;i++)belong[i]=(i-1)/S+1;
    for(int i=1;i<=cnt;i++)st[i]=(i-1)*S+1,ed[i]=min(n,i*S);
    for(int i=1;i<=cnt;i++)build(i);
    int op,x,y,l,r;
    ll k,tmp,ans;
    m=getint();
    while(m--)
    {
        op=getint(),x=getint(),y=getint();
        l=belong[x],r=belong[y];
        if(!op)
        {
            k=getint();
            tmp=k*(st[l+1]-x+1);
            for(int i=l+1;i<r;i++)
            {
                fir[i]+=tmp,d[i]+=k;
                tmp+=S*k;
            }
            pushdown(l);
            tmp=k;
            for(int i=x;i<=min(y,ed[l]);i++)
                a[i]+=tmp,tmp+=k;
            build(l);
            if(l!=r)
            {
                pushdown(r);
                tmp=k*(st[r]-x+1);
                for(int i=st[r];i<=y;i++)a[i]+=tmp,tmp+=k;
            }
            tmp=k*(y-x+1);
            for(int i=y+1;i<=ed[r];i++)a[i]+=tmp;
            build(r);
            for(int i=r+1;i<=cnt;i++)add[i]+=tmp;
        }
        else
        {
            ans=-INF;
            for(int i=l+1;i<r;i++)ans=max(ans,query(i));
            for(int i=x;i<=min(y,ed[l]);i++)ans=max(ans,calc(i));
            if(l!=r)for(int i=st[r];i<=y;i++)ans=max(ans,calc(i));
            printf("%lld\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值