WC2010 BZOJ1758 重建計劃_長鏈剖分
題目大意:
求長度$\in [L,U]$的路徑的最大邊權和平均值。
題解
首先二分就不用說了,分數規劃大家都懂。
這題有非常顯然的點分治做法,但還是借著這個題學一波長鏈剖分。
其長鏈剖分本身也沒啥,就是重鏈剖分中判斷中兒子的參數由比較子樹大小改為了子樹最深點的深度。
這樣一來,有一個很顯然的性質,所有長鏈長度值和$<n$,然而這是廢話,因為整棵樹的邊也只有$n-1$條。
長鏈剖分還有一個非常強大的能力,可以在線性的時間內合並以深度為下標的子樹信息。
對於節點$x$和其長鏈連向的兒子$y$,由於在$Dfs$序中$x$就在$y$左邊且挨著,所以不用合並,由於要合並的信息恰好以深度為下標,所以直接讓$x$“繼承”$y$的信息即可,接著考慮將其他兒子合並到$x$上。
對於任意其他的兒子$z$,我們直接暴力將它們合並即可,就是枚舉每個$z$的每個深度,將它插入$x$處的數組中。所有點被暴力插入的復雜度是每一個不在父節點的長鏈上的點所在的長鏈的長度$=$所有長鏈的長度$=O(n)$。
換作這道題有什麽用呢,假設每一個點$x$維護一個從$x$出發向下走到深度$D$的權值最大的鏈,就可以把它存在$Dfs$序的數組上,因為這恰是一個以深度為下標並且容易合並的東西。至於更新答案,只需要對於每一次暴力插入信息時,枚舉深度的同時用線段樹在當前$x$所占有的區間上查詢一下即可。
還有一個小技巧,對於$lca(x,y)=m,len(x,y)=Dis[x]+Dis[y]-2\times Dis[m]$,其中$Dis[x]$表示按照當前二分的結果$x$到一號點的距離(不是深度),只需要維護$Dis[x]$即可,這樣就避免了因為枚舉的$m$不斷往上走產生的大量區間修改操作。
於是最終復雜度為$O(n\cdot \log n \cdot\log V)$。
當然這道題應該還可以通過預處理和單調隊列來達到$O(n\cdot \log n+n\cdot\log V)$。
然而我不會......
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 200020 #define INF 4000000000000000ll #define mid ((l+r)>>1) using namespace std; LL read(){ LL 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; } LL n,m,L,R,ans,U,D,mxd[M],mxs[M],dfn[M],last[M],dst[M]; LL fs[M],nt[M<<1],to[M<<1],len[M<<1],cnt,tmp,res,S[M]; LL dis[M],p[M<<2],dep[M]; bool cmp(LL x,LL y){return mxd[x]<mxd[y];} void link(LL x,LL y,LL dt){nt[tmp]=fs[x],fs[x]=tmp,len[tmp]=dt,to[tmp++]=y;} void dfs1(LL x,LL last){ mxd[x]=dep[x]; for(LL i=fs[x];i!=-1;i=nt[i]){ if(to[i]==last) continue; dep[to[i]]=dep[x]+1,dst[to[i]]=dst[x]+len[i],dfs1(to[i],x); if(mxd[to[i]]>mxd[x]) mxd[x]=mxd[to[i]],mxs[x]=to[i]; } } void dfs2(LL x,LL last){ dfn[x]=++cnt; if(mxs[x]) dfs2(mxs[x],x);else return; for(LL i=fs[x];i!=-1;i=nt[i]) if(to[i]!=last&&to[i]!=mxs[x]) dfs2(to[i],x); } void ins(LL x,LL l,LL r,LL pos,LL num){ p[x]=max(p[x],num); if(l==r) return; if(pos<=mid) ins(x<<1,l,mid,pos,num); else ins(x<<1|1,mid+1,r,pos,num); } LL query(LL x,LL l,LL r,LL ls,LL rs){ if(r<ls||rs<l||rs<ls) return -INF; if(ls<=l&&r<=rs) return p[x]; return max(query(x<<1,l,mid,ls,rs),query(x<<1|1,mid+1,r,ls,rs)); } void DP(LL x,LL last){ dis[x]=dst[x]-m*(dep[x]-1),ins(1,1,n,dfn[x],dis[x]); if(!mxs[x]) return; DP(mxs[x],x); for(LL i=fs[x];i!=-1;i=nt[i]){ if(to[i]==mxs[x]||to[i]==last) continue; DP(to[i],x); if(ans>=0) return; for(LL pos=0;pos+dep[to[i]]<=mxd[to[i]];pos++){ LL t1=D-pos-1,t2=min(U-pos-1,mxd[x]-dep[x]); S[pos]=query(1,1,n,dfn[to[i]]+pos,dfn[to[i]]+pos); ans=max(ans,query(1,1,n,dfn[x]+t1,dfn[x]+t2)+S[pos]-(dis[x]*2)); if(ans>=0) return; } for(LL pos=0;pos+dep[to[i]]<=mxd[to[i]];pos++){ ins(1,1,n,dfn[x]+pos+1,S[pos]); } } ans=max(ans,query(1,1,n,dfn[x]+D,dfn[x]+min(U,mxd[x]-dep[x]))-dis[x]); } int main(){ n=read(),D=read(),U=read(),memset(fs,-1,sizeof(fs)); for(LL i=1;i<n;i++){ LL u=read(),v=read(),dt=read(); dt*=2000ll,link(u,v,dt),link(v,u,dt); } dep[1]=1,dfs1(1,0),dfs2(1,0); for(L=0,R=2000000000ll;L<=R;){ for(LL i=1;i<=(n<<2);i++) p[i]=-INF; m=((R>>1)+(L>>1)+(L&R&1)); ans=-1,DP(1,0); if(ans<0) R=m-1; else res=m,L=m+1; } if(res&1) res++; res>>=1; printf("%lld.%03lld\n",res/1000,res%1000); return 0; }
WC2010 BZOJ1758 重建計劃_長鏈剖分