P1315 观光公交

传送门

显然答案等于初始时的答案减去最多可以减少的代价

考虑在某个路段 $[l,l+1]$ 用一次加速的影响,设 $r$ 为 $l$ 往右(不包括 $l$)第一个车要等人的站,那么所有下车的站在 $[l+1,r]$ 之间的人都可以少一秒

设第 $i$ 个站下车 $leav[i]$ 人,那么贡献即为 $sum_{i=l+1}^{r}leav[i]$ 

每次加速以后车到后面一段的站的时间都会减少,直到某个站从人等车变成车等人,那么此时的 $r$ 也就变成了那个站

考虑在 $l$ 最多能用几次加速并且保证每次贡献都是一样的,显然是从 $l+1$ 到 $r-1$ 之间所有站人等车的时间最小值,再之后 $r$ 变化了所以贡献也变了(此时先不考虑 $K$ 和 $D$ 的限制)

考虑贪心,每次都选择贡献最大的位置加速并动态维护各种东西,如何证明正确性

发现当前所有车等人的站把路程分成了很多部分,每一次加速贡献只和加速位置到这一部分右端点离开的人数有关

随着操作大段只会被分成更多小段,所以每次都选贡献最大的操作即可

具体就是首先每一段互不影响,设 $x,y$ 在同一段,如果 $x$ 的贡献比 $y$ 大(即 $x<y$),操作 $x$ 或 $y$ 都最终会导致 $z$ 位置($z>=y$)首先变成车等人,那么操作 $y$ 显然还不如操作 $x$ 

如果 $x$ 贡献比 $y$ 大,并且操作 $x$ 会导致 $z$ 位置($z<y$) 首先变成车等人,那么我们就算把 $z$ 断开了也不会影响到 $y$ 的贡献,所以每次取最优即可

然后就是如何维护的问题了,我们要动态维护的内容有:

当前每个位置加速的贡献,并且求全局最大值以及位置

当前每个位置人要等车多久,并且求区间最小值以及位置

当前每个位置往右第一个车等人的位置

用线段树维护复杂度 $O(n log n)$,长代码警告,一堆细节警告

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
inline ll read()
{
    ll 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;
const ll INF=1e18;
ll n,m,K,D[N],las[N],tim[N],Ans;
// D就是题目的D,las[i]存每个位置最晚到达的人,tim[i]存初始公交车到达每个站的时间
ll t[N],L[N],R[N],leav[N],num[N];
// t[i]是游客i到达的时间,L[i]是游客i的起始站,R[i]是游客i的终点站
// leav[i]是每个站下车的人数,num[i]维护每个位置i往右第一个人等车的站r之间([i+1,r])下车的人数
struct dat {
    ll val,pos;
    dat(ll _val=0,ll _pos=0) { val=_val; pos=_pos; }
    inline bool operator < (const dat &tmp) const {
        return val<tmp.val;
    }
};
struct Segtree {
    dat mx[N<<2],mi[N<<2];//最大的贡献及位置,最小的时间差及位置
    ll nxt[N],mxt[N<<2],mit[N<<2],nxtt[N<<2];
    //每个位置往右第一个车等人的站,mx的增加标记,mi的增加标记,nxt的覆盖标记
    inline void pushup(int o)
    {
        int lc=o<<1,rc=o<<1|1;
        mx[lc]<mx[rc] ? mx[o]=mx[rc] : mx[o]=mx[lc];
        mi[rc]<mi[lc] ? mi[o]=mi[rc] : mi[o]=mi[lc];
    }
    inline void pushdown(int o,int l,int r)
    {
        if(!mxt[o]&&!mit[o]&&!nxtt[o]) return;
        int lc=o<<1,rc=o<<1|1;
        if(mxt[o])
        {
            if(l!=r) mxt[lc]+=mxt[o],mxt[rc]+=mxt[o];
            mx[o].val+=mxt[o]; mxt[o]=0;
        }
        if(mit[o])
        {
            if(l!=r) mit[lc]+=mit[o],mit[rc]+=mit[o];
            mi[o].val+=mit[o]; mit[o]=0;
        }
        if(nxtt[o])
        {
            if(l==r) nxt[l]=nxtt[o];
            else nxtt[lc]=nxtt[rc]=nxtt[o];
            nxtt[o]=0;
        }
    }
    void build(int o,int l,int r)
    {
        if(l==r) { mx[o]=dat(num[l],l); mi[o]=dat(tim[l]-las[l],l); return; }
        int mid=l+r>>1; build(o<<1,l,mid); build(o<<1|1,mid+1,r);
        pushup(o);
    }
    void change_mx(int o,int l,int r,int ql,int qr,ll v)
    {
        pushdown(o,l,r); if(l>qr||r<ql) return;
        if(l>=ql&&r<=qr) { mxt[o]=v; pushdown(o,l,r); return; }
        int mid=l+r>>1;
        change_mx(o<<1,l,mid,ql,qr,v); change_mx(o<<1|1,mid+1,r,ql,qr,v);
        pushup(o);
    }
    void change_mi(int o,int l,int r,int ql,int qr,ll v)
    {
        pushdown(o,l,r); if(l>qr||r<ql) return;
        if(l>=ql&&r<=qr) { mit[o]=v; pushdown(o,l,r); return; }
        int mid=l+r>>1;
        change_mi(o<<1,l,mid,ql,qr,v); change_mi(o<<1|1,mid+1,r,ql,qr,v);
        pushup(o);
    }
    void change_nxt(int o,int l,int r,int ql,int qr,ll v)
    {
        pushdown(o,l,r); if(l>qr||r<ql) return;
        if(l>=ql&&r<=qr) { nxtt[o]=v; pushdown(o,l,r); return; }
        int mid=l+r>>1;
        change_nxt(o<<1,l,mid,ql,qr,v); change_nxt(o<<1|1,mid+1,r,ql,qr,v);
        pushup(o);
    }
    dat query_mx(int o,int l,int r,int ql,int qr)
    {
        pushdown(o,l,r); if(l>qr||r<ql) return dat(-INF,0);
        if(l>=ql&&r<=qr) return mx[o];
        int mid=l+r>>1;
        dat res1=query_mx(o<<1,l,mid,ql,qr);
        dat res2=query_mx(o<<1|1,mid+1,r,ql,qr);
        return res1<res2 ? res2 : res1;
    }
    dat query_mi(int o,int l,int r,int ql,int qr)
    {
        pushdown(o,l,r); if(l>qr||r<ql) return dat(INF,0);
        if(l>=ql&&r<=qr) return mi[o];
        int mid=l+r>>1;
        dat res1=query_mi(o<<1,l,mid,ql,qr);
        dat res2=query_mi(o<<1|1,mid+1,r,ql,qr);
        return res1<res2 ? res1 : res2;
    }
    ll query_nxt(int o,int l,int r,int pos)
    {
        pushdown(o,l,r); if(l==r) return nxt[l];
        int mid=l+r>>1;
        ll res= pos<=mid ? query_nxt(o<<1,l,mid,pos) : query_nxt(o<<1|1,mid+1,r,pos);
        pushup(o); return res;
    }
}T;//很长的线段树(但是不难)

int main()
{
    n=read(),m=read(),K=read();
    for(int i=1;i<n;i++) D[i]=read();
    for(int i=1;i<=m;i++)
    {
        t[i]=read(),L[i]=read(),R[i]=read();
        leav[R[i]]++; las[L[i]]=max(las[L[i]],t[i]);
    }
    for(int i=n-1;i>=1;i--) leav[i]+=leav[i+1];
    for(int i=2;i<=n;i++) tim[i]=max(tim[i-1],las[i-1])+D[i-1];
    for(int i=1;i<=m;i++) Ans+=tim[R[i]]-t[i];
    T.nxt[n]=n+1;
    for(int i=n-1;i>=1;i--)
    {
        T.nxt[i]= tim[i+1]<=las[i+1] ? i+1 : T.nxt[i+1];
        num[i]=leav[i+1]-leav[T.nxt[i]+1];
    }
    T.build(1,1,n);
    while(K)//最恶心的细节在这里
    {
        if(T.mx[1].val<=0) break;
        dat res=T.query_mx(1,1,n,1,n);//注意这里要query不要直接把 T.mx[1] 拿出来,因为可能标记还没下传(我的线段树的标记下传了才生效...)
        int l=res.pos, r=T.query_nxt(1,1,n,l);
        dat z=T.query_mi(1,1,n,l+1,r-1);
        ll mx=min( min(D[l],K) , z.val );//记得K,D的限制
        Ans-=mx*res.val; D[l]-=mx; K-=mx;
        T.change_mi(1,1,n,l+1,r-1,-mx);
        if(mx==z.val)//如果之后某个站变成车等人了,维护nxt,mx
            T.change_nxt(1,1,n,l,z.pos-1,z.pos),
            T.change_mx(1,1,n,l,z.pos-1, -(leav[z.pos+1]-leav[r+1]) );//注意区间是什么
        if(!D[l]) T.change_mx(1,1,n,l,l,-INF);//注意这里如果没法再加速了就改成最小值
    }
    printf("%lld
",Ans);
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/11580263.html