BZOJ2388: 旅行规划

14 篇文章 0 订阅
3 篇文章 0 订阅

分块,每一块维护前缀和,观察发现,一次区间加操作对于 L 前,R后的区间里相对的大小关系没有影响,对于 [L,R] 里,将每个位置放在平面内作为一个点 (i,si) ,那么相当于给区间里加了一个斜率。
每个块维护一个凸包(上凸下凸应该都可以,下文以上凸为例),如果这个块被完整覆盖,就打个斜率的标记,否则暴力重构凸包。
询问时在区间里二分,找到一个最大的斜率 (u,v) 使得其加上区间的斜率标记 <0 <script type="math/tex" id="MathJax-Element-792"><0</script>,这时因为上凸,斜率递减,而这之前的斜率加上标记全部 >0 ,所以 u 优于其左侧所有点,这条斜率开始之后的斜率加标记全部<0,所以v优于其右侧所有点,u优于v,所以u是最大值



code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 110000;
const int maxnn = 400;
struct point
{
    ll x,y;
    point(){}
    point(ll _x,ll _y){x=_x;y=_y;}
};
struct node
{
    ll s[maxnn];
    point p[maxnn];
    int pos[maxnn];
    int len;
    ll k;
}a[maxnn];
ll s[maxn],f[maxn];
int id[maxn],st[maxn],N;
int n,m;

ll multi(point a1,point a2,point x)
{
    a1.x-=x.x;a2.x-=x.x;
    a1.y-=x.y;a2.y-=x.y;
    return a1.x*a2.y-a2.x*a1.y;
}
double get_k(point x,point y)
{
    double tx=y.x-x.x,ty=y.y-x.y;
    return ty/tx;
}
void up(ll &x,ll y){if(y>x)x=y;}

void _build(int t)
{
    a[t].len=a[t].k=f[t]=0;
    int len=0;
    for(int i=st[t];i<st[t+1];i++)
    {
        point tmp=point(i-st[t]+1,s[i]);
        while(len>1&&multi(a[t].p[len],tmp,a[t].p[len-1])>0) len--;
        a[t].p[++len]=tmp;
        a[t].pos[len]=i;
    }
    a[t].len=len;
}
int find_(int t)
{
    int l=1,r=a[t].len-1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(get_k(a[t].p[mid],a[t].p[mid+1])+(double)a[t].k>0)l=mid+1;
        else r=mid-1;
    }
    return r+1;
}

int main()
{
    scanf("%d",&n);N=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&s[i]); s[i]+=s[i-1];
        id[i]=(i-1)/N+1;
    }
    for(int i=1;i<=id[n];i++)st[i]=(i-1)*N+1;
    st[id[n]+1]=n+1;
    for(int i=1;i<=id[n];i++) _build(i);

    scanf("%d",&m);
    while(m--)
    {
        int x;scanf("%d",&x);
        if(!x)
        {
            int l,r;ll k;scanf("%d%d%lld",&l,&r,&k);
            int t1=id[l],t2=id[r];
            if(t1==t2)
            {
                for(int i=st[t1];i<st[t1+1];i++)
                    s[i]=s[i]+a[t1].k*i+f[t1];
                for(int i=l;i<=r;i++)
                    s[i]+=k*(i-l+1);
                for(int i=r+1;i<st[t1+1];i++) s[i]+=(r-l+1)*k;
                _build(t1);
                for(int i=t1+1;i<=id[n];i++) f[i]+=(r-l+1)*k;
            }
            else
            {
                for(int i=st[t1];i<st[t1+1];i++)
                    s[i]=s[i]+a[t1].k*i+f[t1];
                for(int i=l;i<st[t1+1];i++) s[i]+=k*(i-l+1);
                _build(t1);
                for(int i=t1+1;i<t2;i++)
                {
                    f[i]-=(l-1)*k;
                    a[i].k+=k;
                }
                for(int i=st[t2];i<st[t2+1];i++)
                    s[i]=s[i]+a[t2].k*i+f[t2];
                for(int i=st[t2];i<=r;i++) s[i]+=k*(i-l+1);
                for(int i=r+1;i<st[t2+1];i++) s[i]+=k*(r-l+1);
                _build(t2);

                for(int i=t2+1;i<=id[n];i++)f[i]+=k*(r-l+1);
            }
        }
        else
        {
            int l,r; scanf("%d%d",&l,&r);
            int t1=id[l],t2=id[r];
            if(t1==t2)
            {
                ll ret=LLONG_MIN;
                for(int i=l;i<=r;i++) 
                    up(ret,s[i]+i*a[t1].k+f[t1]);
                printf("%lld\n",ret);
            }
            else
            {
                ll ret=LLONG_MIN;
                for(int i=l;i<st[t1+1];i++) 
                    up(ret,s[i]+i*a[t1].k+f[t1]);
                for(int i=st[t2];i<=r;i++)
                    up(ret,s[i]+i*a[t2].k+f[t2]);
                for(int i=t1+1;i<t2;i++)
                {
                    int temp=find_(i); temp=a[i].pos[temp];
                    up(ret,s[temp]+a[i].k*temp+f[i]);
                }
                printf("%lld\n",ret);
            }
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值