[Luogu P2387] [NOI2014]魔法森林 LCT維護邊權
題面
傳送門:https://www.luogu.org/problemnew/show/P2387
Solution
這題的思想挺好的。
對於這種最大值最小類的問題,很自然的可以想到二分答案。很不幸的是,這題是雙關鍵字排序的,我們怎麼二分呢?
先二分a再二分b?怎麼看都布星啊。
那a+b作為關鍵字二分?也布星啊。
那咋搞啊?
不如,我們換個想法,我們把其中一個關鍵字列舉,再看在這個關鍵字的限制下,另外一個儘可能小。
仔細想想,應該是能覆蓋到所有的情況的。
所以說,我們可考慮這樣做:我們先列舉a的大小(即所選的邊的a必須小於這個值),在滿足前者的條件下,使得從出發先到終點的路上的最大的b儘可能小。
對於第二個問題,是不是很眼熟?沒錯,這個問題就是著名的原題貨車運輸:我們要使得路徑上經過的b值的最大值最小,這條路徑一定是在以b為關鍵字的最小生成樹上的(具體證明請移步貨車運輸那道題的題解)。
所以說,我們現在研究的問題就變為了如何快速的維護一個變化的最小生成樹。
快速維護變化的樹,我們可以很自然地想到使用LCT來維護。再結合我們之前維護動態最小生存樹的知識:每加入一條邊,它必定會連線兩個點而形成一個環,我們要判斷這條邊是否會在新的生成樹上,只需要看一下環上的最大的邊權和這條邊的關係就好了,如果這條邊的邊權比環上的最大值還要小,我們就可以把環上的那條最大的邊斷開,接上我們這條新的邊。否則的話,這條邊一定不會成為新的最小生成樹的一部分。
所以說,我們的LCT只需要在每新加入一條邊時,檢查其連線的兩端是否是聯通的。如果不聯通的話,加入這條邊一定是沒有問題的。如果聯通的話,就把所連兩端的鏈split出來,找到最大值,比較一下大小關係就好。
至於如何用LCT維護邊,我的方法是用點來代替邊,即一條邊以一個有連向它的兩個端點的邊的點來替代。具體寫法可以參照一下程式碼。
時間複雜度為$O(n*logn*logn)$
Code
#include<iostream> #include<cstdio> #include<algorithm> using namespacestd; long long read() { long long x=0,f=1; char c=getchar(); while(!isdigit(c)){if(c=='-') f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int M=100000+100; const int N=50000+100; const int T=N+M; struct road { int s,t,a,b; }e[M]; int n,m; bool cmp(road x,road y) { return x.a<y.a; } struct LCT { int son[T][2],fa[T],lazy[T],MAX[T],num[T],mstack[T],top; inline void Update(int x) { MAX[x]=0; if(num[MAX[son[x][0]]]>=num[x] and num[MAX[son[x][0]]]>=num[MAX[son[x][1]]]) MAX[x]=MAX[son[x][0]]; if(num[MAX[son[x][1]]]>=num[x] and num[MAX[son[x][1]]]>=num[MAX[son[x][0]]]) MAX[x]=MAX[son[x][1]]; if(num[x]>=num[MAX[son[x][0]]] and num[x]>=num[MAX[son[x][1]]]) MAX[x]=x; } inline void Mirror(int x) { lazy[x]=!lazy[x],swap(son[x][0],son[x][1]); } inline void PushDown(int x) { if(lazy[x]==0) return; lazy[x]=0; Mirror(son[x][0]),Mirror(son[x][1]); } inline bool IsRoot(int x) { return x!=son[fa[x]][0] and x!=son[fa[x]][1]; } inline void Rotate(int x,int type) { int y=fa[x],z=fa[y]; if(IsRoot(y)==false) son[z][y==son[z][1]]=x; fa[x]=z; son[y][!type]=son[x][type],fa[son[x][type]]=y; son[x][type]=y,fa[y]=x; Update(y),Update(x); } inline void Splay(int x) { mstack[top=1]=x; for(int i=x;i!=0;i=fa[i]) mstack[++top]=fa[i]; for(int i=top;i>=1;i--) PushDown(mstack[i]); while(IsRoot(x)==false) { if(x==son[fa[x]][fa[x]==son[fa[fa[x]]][1]] and IsRoot(fa[x])==false) Rotate(fa[x],x==son[fa[x]][0]), Rotate(x,x==son[fa[x]][0]); else Rotate(x,x==son[fa[x]][0]); } } void Access(int x) { for(int t=0;x!=0;t=x,x=fa[x]) Splay(x),son[x][1]=t,fa[t]=x,Update(x); } inline void MakeRoot(int x) { Access(x),Splay(x); Mirror(x); } inline int FindRoot(int x) { Access(x),Splay(x); while(son[x][0]!=0) PushDown(x),x=son[x][0]; Splay(x); return x; } inline void Link(int x,int y)//x->y { if(FindRoot(x)==FindRoot(y)) return; MakeRoot(x); fa[x]=y; } inline void Split(int x,int y)//root:y { MakeRoot(x); Access(y),Splay(y); } inline void Cut(int x,int y) { Split(x,y); if(x==son[y][0] and fa[x]==y) { son[y][0]=fa[x]=0; Update(y); } } inline int Query(int x,int y) { MakeRoot(x); Access(y),Splay(y); return MAX[y]; } inline void AddLine(int x) { if(e[x].s==e[x].t) return;//自環 num[n+x]=e[x].b,MAX[n+x]=n+x; if(FindRoot(e[x].s)!=FindRoot(e[x].t)) { Link(n+x,e[x].s),Link(n+x,e[x].t); return ; } int t=Query(e[x].s,e[x].t); if(num[n+x]<num[t]) { Cut(e[t-n].s,t); Cut(e[t-n].t,t); Link(e[x].s,n+x); Link(e[x].t,n+x); } } inline int Query2() { if(FindRoot(n)!=FindRoot(1)) return 0x3f3f3f3f; return num[Query(1,n)]; } }lct; int main() { n=read(),m=read(); for(int i=1;i<=m;i++) e[i].s=read(),e[i].t=read(),e[i].a=read(),e[i].b=read(); sort(e+1,e+1+m,cmp); int ans=0x3f3f3f3f; for(int i=1;i<=m;i++) { lct.AddLine(i); ans=min(ans,e[i].a+lct.Query2()); } if(ans==0x3f3f3f3f) printf("-1"); else printf("%d",ans); return 0; }