【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。

题解:

          设s[i]为i位置美观度的前缀和.

          把(i,s[i])看成一个点,考虑分块.

          对于每一块,维护一个上凸壳,对于整块的询问就可以在凸壳上二分.

          修改的时候可以发现是加一个等差数列,所以对于整块的可以直接打标记.

          边缘的部分暴力修改然后暴力重构凸壳.

          对于每次修改的右端点之后的块,等于是整块加一个值,

          这样最优值的位置是不变的,所以直接打标记即可.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100010
#define M 400
#define ll long long
#define inf 1LL<<60 
using namespace std;
int bk,n,m,cnt,kind,x,y,bl[N],l[M],r[M],st[N],xu[M][M],size[M];
ll s[N],f[M],d[M],c[M],k,temp;
int read(){
  int x(0),f(1);char ch=getchar();
  while (ch<'0'||ch>'9') { if (ch=='-')f=-1;ch=getchar();}
  while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
  return x*f;
}
void getbk(){
  bk=sqrt(n);
  if (n%bk) cnt=n/bk+1;
  else cnt=n/bk;
  for (int i=1;i<=n;i++)
    bl[i]=(i-1)/bk+1;
  for (int i=1;i<=cnt;i++){
    l[i]=(i-1)*bk+1;
    r[i]=min(n,bk*i);
  }
}
double cal(int x,int y){
  return (double)(s[y]-s[x])/(double)(y-x); 
} 
void build(int x){
  int top=0;
  st[++top]=l[x];
  for (int i=l[x]+1;i<=r[x];i++){
    while (top>1&&cal(st[top-1],st[top])<cal(st[top-1],i)) top--;
    st[++top]=i;
  }
  st[0]=0;st[top+1]=n+1;size[x]=top;
  for (int i=0;i<=top+1;i++) xu[x][i]=st[i];
}
void pushdown(int x){
  temp=f[x];
  for (int i=l[x];i<=r[x];i++){
     s[i]+=temp;temp+=d[x];s[i]+=c[x];
  }
  f[x]=c[x]=d[x]=0; 
}
void change(int x,int y,ll k){
  int a=bl[x],b=bl[y];
  temp=k*(l[a+1]-x+1);
  for (int i=a+1;i<b;i++){
     f[i]+=temp;d[i]+=k;
     temp+=k*(r[i]-l[i]+1); 
  }
  pushdown(a);temp=k;
  for (int i=x;i<=min(y,r[a]);i++){
    s[i]+=temp;temp+=k;
  }
  build(a);pushdown(b);
  if (a!=b){
    temp=k*(l[b]-x+1);
    for (int i=l[b];i<=y;i++){
      s[i]+=temp;temp+=k;
    } 
  }
  temp=k*(y-x+1);
  for (int i=y+1;i<=r[b];i++)
    s[i]+=temp;
  build(b);
  for (int i=b+1;i<=cnt;i++)
    c[i]+=temp;
}
ll getval(int x){
  ll ans(0);
  if (x==0||x==n+1) return -inf;
  int t=bl[x];
  ans=s[x]+c[t];
  ans+=(x-l[t])*d[t]+f[t];
  return ans;
}
ll ask(int x){
  int a=1,b=size[x];
  while (a<=b){
    int mid=(a+b)>>1;
    ll t1=getval(xu[x][mid-1]);
    ll t2=getval(xu[x][mid]);
    ll t3=getval(xu[x][mid+1]);
    if (t1<t2&&t2<t3) a=mid+1;
    else if (t3<t2&&t2<t1) b=mid-1;
    else return t2;     
  }
}
ll query(int x,int y){
  int a=bl[x],b=bl[y];
  ll ans=-inf; 
  for (int i=a+1;i<b;i++)
    ans=max(ans,ask(i));
  for (int i=x;i<=min(y,r[a]);i++)
    ans=max(ans,getval(i));
  if (a!=b){
    for (int i=l[b];i<=y;i++)
      ans=max(ans,getval(i));
  }
  return ans;
}
int main(){
  n=read();
  for (int i=1;i<=n;i++){
    x=read();
    s[i]=s[i-1]+x;
  } 
  getbk();
  for (int i=1;i<=cnt;i++) build(i);
  m=read();
  for (int i=1;i<=m;i++){
    kind=read();x=read();y=read();
    if (kind==0){k=read();change(x,y,k);}
    else printf("%lld\n",query(x,y));
  }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值