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\n",Ans); return 0; }