P2496 [SDOI2012]体育课

传送门

分块

对每个块维护一个 $add$ 和 $del$ 标记,对于块 $o$ 内某个位置 $i$,它真实的修改量为 $a[i]+add[o]*i-del[o]$

这样就可以维护一个区间加一个等差数列的操作了

对于操作 $2$,交换两个位置,直接把两个位置的块标记下传,然后直接交换

对于操作 $1$,考虑到相同的块内 $add$ 只会越来越大,对于某个位置 $i$,它一开始是最大的位置,随着标记的增加,比它大的位置显然一定在它右边

所以考虑维护一个数列,使得数列相邻两个位置中,一旦左边的被超越,下一个可能的最大值就是右边下一个

我一开始天真地以为直接维护一个单调数列即可,然后搞了半天发现是错的,如图:

 

 对于这种情况,当 $a[j]$ 还没超过 $a[i]$ 时,$a[k]$ 可能已经超过 $a[i]$ 了,所以我们不能把 $a[j]$ 放到数列里,不然就无法保证单调性

事实上,我们需要维护的是所有点对 $(x,a[x])$ 构成的上凸包,具体理由如下:

对于某个位置 $i$,如果 $a[i]$ 被超越,那么对于下一个位置 $k$ ,$i,k$ 之间必须不存在 $j$,使得

$a[i]+add*i-del>a[j]+add*j-del$ 并且 $a[k]+add*k-del>a[i]+add*i-del$

即 $(a[i]-a[j])/(i-j)<-add$ 且 $(a[i]-a[k])/(i-k)>-add$

即 $(a[i]-a[k])/(i-k)>(a[i]-a[j])/(i-j)$,发现左右两边其实就是连线的斜率,所以即维护一个上凸包

具体实现起来还是有一些细节的

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
inline 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<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7,M=357;
int n,m;
int bel[N],L[M],pos[M];
ll a[N],add[M],del[M];
vector <int> st[M];
inline void push_down(int o)
{
    for(int i=L[o];i<L[o+1];i++) a[i]+=add[o]*i-del[o];
    add[o]=del[o]=pos[o]=0; st[o].clear();
}
inline ll calc(int i,int o) { return a[i]+add[o]*i-del[o]; }
inline void upd(int o)
{
    while(pos[o]<st[o].size()-1 && calc(st[o][pos[o]],o)<=calc(st[o][pos[o]+1],o)  ) pos[o]++;
}
inline void build(int o)
{
    for(int i=L[o];i<L[o+1];st[o].push_back(i),i++)
        while(st[o].size()>1 && 
        (a[i]-a[st[o][st[o].size()-1]])*(st[o][st[o].size()-1]-st[o][st[o].size()-2]) >=
        (a[st[o][st[o].size()-1]]-a[st[o][st[o].size()-2]])*(i-st[o][st[o].size()-1]) ) st[o].pop_back();
    upd(o);
}
inline void query(int l,int r)
{
    int bl=bel[l-1]+1,br=bel[r+1]-1; ll res=0;
    if(bl>br)
    {
        for(int i=l;i<=r;i++) res=max(res, calc(i,bel[i]) );
        printf("%lld\n",max(0ll,res-calc(1,1))); return;
    }
    for(int i=l;i<L[bl];i++) res=max(res, calc(i,bel[i]) );
    for(int i=L[br+1];i<=r;i++) res=max(res, calc(i,bel[i]) );
    for(int i=bl;i<=br;i++) res=max(res, calc(st[i][pos[i]],i) );
    printf("%lld\n",max(0ll,res-calc(1,1)));
}
inline void Swap(int x,int y)
{
    push_down(bel[x]); push_down(bel[y]);
    swap(a[x],a[y]);
    build(bel[x]); build(bel[y]);
}
inline void change(int l,int r,int t)
{
    int bl=bel[l-1]+1,br=bel[r+1]-1;
    if(bl>br)
    {
        push_down(bel[l]); push_down(bel[r]);
        for(int i=l;i<=r;i++) a[i]+=1ll*(i-l+1)*t;
        build(bel[l]); build(bel[r]); return;
    }
    if(L[bl]!=l)
    {
        for(int i=l;i<L[bl];i++) a[i]+=1ll*(i-l+1)*t;
        push_down(bl-1); build(bl-1);
    }
    if(L[br+1]-1!=r)
    {
        for(int i=L[br+1];i<=r;i++) a[i]+=1ll*(i-l+1)*t;
        push_down(br+1); build(br+1);
    }
    for(int i=bl;i<=br;i++) add[i]+=t,del[i]+=1ll*(l-1)*t,upd(i);
}
int main()
{
    n=read(),m=read(); int T=sqrt(n)+1;
    for(int i=1;i<=n;i++)
    {
        a[i]=read(); bel[i]=(i-1)/T+1;
        if(bel[i]!=bel[i-1]) L[bel[i]]=i;
    }
    bel[n+1]=bel[n]+1; L[bel[n+1]]=n+1;
    for(int i=1;i<=bel[n];i++) build(i);
    int opt,a,b;
    for(int i=1;i<=m;i++)
    {
        opt=read(); a=read(),b=read();
        if(opt==1) { query(a,b); continue; }
        if(opt==2) { Swap(a,b); continue; }
        change(a,b,read());
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/LLTYYC/p/11489575.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值