NOIP2011 觀光公交 加強版
題目大意
給定從左到右的$n$個車站以及兩兩之間通行的需要的時間。
有$m$個人,第$i$個人會在$T_i$時刻出現在$a_i$車站,目的地是$b_i$。
一輛車第$0$時刻出現在一號站臺,從左向右駛去,每經過一個車站(包括$1$號),它會等待知道所有應該在該站臺出現的乘客都出現才會繼續駛向下一個站臺。
定義一個人花費的時間是列車到達$b_i$的時刻$-T_i$。你有$k$次機會使得某相鄰兩站花費的時間減少一個時間單位(需要始終保證非負),求這$m$個人花費時間之和的最小值。
題解
在$n,m,k$均很小的情況下可以用各種演算法搞過去,但加強後不得不優化複雜度。
前置技能:$O(nk)$的貪心(你可以在洛谷中找到大量的題解)
先設一個最終答案為$ans$,初始時$ans=-\sum T_i$
仍是考慮設第$i$個車站最後一個人到達時間為$G_i$,到達$i$的時間比$G_i$多出了$S_i$的時間,第$i$個車站到第$i+1$個車站的距離為$D_i$。
那麼$S_i=\max\{S_{i-1}+G_{i-1}+D_{i-1}-G_{i}\},ans+=S_i+G_i$
考慮一段區間$[L,R]$,若有$(L,R]$內的$S$均大於$0$,那麼減少$D_L K$個單位可以使得所有$(L,R]$的到達時間減少$K$,那麼最終答案減少的就是$K\times$區間內是終點的數量。
考慮用線段樹維護$S$,將每一個有意義的$[L,R]$用一個大根堆存起來(以區間內終點數量為關鍵字)。
每次取出堆頂$[L,R]$,求$x=\min\{D_L,\min\{S_i\}i\in(L,R]\}$即為區間內可以減少的時間,那麼將$k$次機會中的$x$個用與減少區間答案。
接下來一定會至少出現一個$i$使得$S_i=0$或$k=0$或$D_L=0$,那麼原來有意義的區間至多分裂成兩個有意義的區間,在分別丟進堆裡不斷處理即可。
可見,該演算法的複雜度及基本與$k$無關,甚至與$m$無關。
複雜度僅為有意義的區間數量(不超過$2n$個)在帶一個線段樹的和堆的$log$,大概就是$O(n\log n)$。
#include<bits/stdc++.h> #define LL long long #define M 600020 using namespace std; namespace IO{ const int BS=(1<<20)+5; char Buffer[BS],*HD,*TL; char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } }using namespace IO; int n,m,p[M<<2],pos[M<<2],tg[M<<2],val[M],D[M],K,last; LL ans,gt[M],st[M]; #define pushup int sn=(p[x<<1]<p[x<<1|1])?(x<<1):(x<<1|1);pos[x]=pos[sn],p[x]=p[sn] struct seg{ int LS,RS;seg(){} seg(int _LS,int _RS){LS=_LS,RS=_RS;} bool operator<(const seg&ot)const{return val[RS]-val[LS]<val[ot.RS]-val[ot.LS];} };priority_queue<seg>H; void build(int x,int l,int r){ if(l==r){pos[x]=r,p[x]=st[r];return;} int mid=((l+r)>>1); build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup; } #define pushdown tg[x<<1]+=tg[x],tg[x<<1|1]+=tg[x],p[x<<1]-=tg[x],p[x<<1|1]-=tg[x],tg[x]=0 void mdf(int x,int l,int r,int ls,int rs,int dt){ if(ls<=l&&r<=rs){tg[x]+=dt,p[x]-=dt;return;} if(r<ls||rs<l) return; int mid=((l+r)>>1); pushdown; mdf(x<<1,l,mid,ls,rs,dt),mdf(x<<1|1,mid+1,r,ls,rs,dt); pushup; } int getnode(int x,int l,int r,int ls,int rs){ if(ls<=l&&r<=rs) return x;if(r<ls||rs<l) return 0; int mid=((l+r)>>1); pushdown; int t1=getnode(x<<1,l,mid,ls,rs); int t2=getnode(x<<1|1,mid+1,r,ls,rs); return p[t1]>p[t2]?t2:t1; } int main(){ n=read(),m=read(),K=read(),last=1,p[0]=1000000000; for(int i=1;i<n;i++) D[i]=read(); for(int i=1;i<=m;i++){ int tim=read(),x=read(),y=read(); gt[x]=max(gt[x],(LL)tim),val[y]++,ans-=tim; } for(int i=2;i<=n;i++) val[i]+=val[i-1]; for(int i=2;i<=n;i++){ st[i]=max(0ll,st[i-1]+gt[i-1]+D[i-1]-gt[i]); if(!st[i]) H.push(seg(last,i)),last=i; ans+=(LL)(val[i]-val[i-1])*(gt[i-1]+st[i-1]+D[i-1]); } if(last<n) H.push(seg(last,n)); build(1,2,n); while(K>0&&!H.empty()){ seg tmp=H.top();H.pop();int ls=tmp.LS,rs=tmp.RS,k=0; if(ls+1<rs) k=getnode(1,2,n,ls+1,rs-1); int dt,ks,t2=D[ls]; dt=min(K,min(t2,p[k])),ks=(dt==t2?ls+1:pos[k]); ans-=(LL)dt*(LL)(val[rs]-val[ls]),K-=dt; if(K<=0) break; if(dt&&ls+1<rs) mdf(1,2,n,ls+1,rs-1,dt); D[ls]-=dt; if(ls<ks) H.push(seg(ls,ks)); if(ks<rs) H.push(seg(ks,rs)); } printf("%lld\n",ans); return 0; }