點此看題面node
大體題意: 給你一張無向圖,每條邊的邊權爲$2^{x_i}$,求$s$到$t$的最短路。c++
最短路,首先考慮$Dijkstra$。這裏用$SPFA$彷佛不太好,由於此題中計算邊權是比較費時間的。git
說句實話,這裏的最短路和普通的最短路是同樣的,惟一區別就是邊權很大。優化
則咱們須要支持的操做就應該是大二進制數的加法和比大小。ui
先考慮最暴力的,咱們對於每一個點,開一個線段樹,每一位維護二進制下這一位的值,表示其距離。spa
而後因爲邊權是$2$的冪,因此也就至關於在二進制下某一位上加上$1$。code
這看似簡單,但要進位啊!並且加法結束後立刻就是比大小,所以這是刻不容緩的。隊列
不過不要緊,咱們能夠研究進位的性質,例以下面這個例子:get
1011101 + 1000 -------- 1100101
這麼一看,性質好像仍是挺顯然的,就是找出高於或等於當前加$1$位(設其爲$x$)的最低的爲$0$的位(設其爲$t$),而後把$x\sim t-1$這些位上改成$0$,把第$t$位改成$1$便可。it
簡單地總結一下,就是要實現區間賦值爲$0$和單點賦值爲$1$兩種操做,這彷佛是線段樹的基本操做?
但新的問題來了:如何求出高於或等於當前加$1$位的最低爲$0$的位?
二分!
考慮當前二分到的位爲$mid$,那麼若$x\sim mid$間的$1$的個數小於等於$t-x$,就說明$x\sim mid$之間存在至少一個$0$,$Check()$返回$true$,不然返回$false$。
而要求$1$的個數,也是線段樹的基本操做吧,記一下每一個點子樹內的$Size$而後區間查詢便可。
如今考慮如何比較兩個大二進制數的大小。
咱們能夠從兩棵線段樹的根節點出發,因爲比較的是最高位,因此若某一節點右兒子內有$1$($Size>0$),另外一節點沒有,就能夠直接比較出大小。
若兩個節點右兒子內都沒有$1$,就去比較左兒子。
但若是兩個節點右兒子內都有$1$,就比較棘手了,由於若是咱們直接去比較右兒子,若是比完以後發現右兒子徹底同樣,咱們又得去比較左兒子,複雜度就退化成了$O(n)$。
仔細思考,即可以發現這個作法錯誤的關鍵就在於兩個右兒子可能徹底同樣沒法比較大小。
那麼若是咱們能快速判斷兩個右兒子是否同樣,不就能夠直接去比較左兒子,而避免這個問題了嗎?
因而就能夠想到哈希,這樣就輕鬆避免了複雜度的退化。
經過上面的總結,咱們能夠發現,用線段樹能夠輕鬆維護這些信息。
可是,以前說的對於每一個點開一棵線段樹顯然不現實,所以就要主席樹。
這樣一來,這道題就完全作完了。
呃,這裏提一下,我在具體實現時碰到一個詭異的問題。
不知道爲何,我寫$Dijkstra$時調用$STL$的優先隊列莫名掛了,調到心態爆炸後手寫了一個線段樹,結果就過了?!
莫名其妙。
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 110000 #define LN 20 #define X 1000000007 #define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v) using namespace std; int n,m,s,t,ee,lnk[N+5];struct edge {int to,nxt,val;}e[(N<<1)+5]; class FastIO { private: #define FS 100000 #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++) #define pc(c) (C==E&&(clear(),0),*C++=c) #define tn (x<<3)+(x<<1) #define D isdigit(c=tc()) int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS]; public: I FastIO() {A=B=FI,C=FO,E=FO+FS;} Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);} Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);} Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);} Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);} I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;} #undef D }F; class ChairmanTree//主席樹 { private: #define L l,mid,O[rt].S[0] #define R mid+1,r,O[rt].S[1] #define PU(x)\ (\ O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz,\ O[x].H=O[O[x].S[0]].H+O[O[x].S[1]].H\ )//上傳信息,維護子樹內1的個數和哈希值 int tot,p[N+5]; class Hash//哈希 { private: #define ull unsigned long long #define RU Reg ull #define CU Con ull ull x,y; public: I Hash() {x=y=0;}I Hash(CU a):x(a),y(a){}I Hash(CU a,CU b):x(a),y(b){} I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);} I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);} I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);} I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;} I bool operator != (Con Hash& o) Con {return x^o.x||y^o.y;} }seed,pw[N+5]; struct node {int Sz,S[2];Hash H;}O[N*LN*10]; I int Chk(CI tl,CI tr,CI l,CI r,CI rt)//求出區間內1的個數,用於檢驗二分 { if(!rt||tl<=l&&r<=tr) return O[rt].Sz;RI mid=l+r>>1; return (tl<=mid?Chk(tl,tr,L):0)+(tr>mid?Chk(tl,tr,R):0); } I int Find(CI rt,CI x)//二分,找出高於或等於當前位的最低的爲0的位 { RI l=x,r=N,t;W(l<r) Chk(x,t=l+r-1>>1,0,N,rt)<=t-x?r=t:l=t+1;//二分 return r; } I void Upt0(CI tl,CI tr,CI l,CI r,int& rt)//區間賦值爲0 { if(O[++tot]=O[rt],rt=tot,tl<=l&&r<=tr) return (void)(rt=0);RI mid=l+r>>1; tl<=mid&&(Upt0(tl,tr,L),0),tr>mid&&(Upt0(tl,tr,R),0),PU(rt); } I void Upt1(CI t,CI l,CI r,int& rt)//單點賦值爲1 { if(O[++tot]=O[rt],rt=tot,l==r) return (void)(O[rt].Sz=1,O[rt].H=pw[l]); RI mid=l+r>>1;t<=mid?Upt1(t,L):Upt1(t,R),PU(rt); } I bool le(CI l,CI r,CI rt1,CI rt2)//比大小 { if(l==r) return O[rt1].Sz<O[rt2].Sz;RI mid=l+r>>1;//若是爲葉節點,直接比較 if(!O[O[rt1].S[1]].Sz&&O[O[rt2].S[1]].Sz) return true;//若是rt1右節點內無1,而rt2內有,說明rt2大 if(O[O[rt1].S[1]].Sz&&!O[O[rt2].S[1]].Sz) return false;//若是rt2右節點內無1,而rt1內有,說明rt1大 if(O[O[rt1].S[1]].H!=O[O[rt2].S[1]].H) return le(mid+1,r,O[rt1].S[1],O[rt2].S[1]);//若是哈希值不一樣,比較右子樹 if(!O[O[rt1].S[0]].Sz) return true;if(!O[O[rt2].S[0]].Sz) return false; return le(l,mid,O[rt1].S[0],O[rt2].S[0]);//比較左子樹 } public: int Rt[N+5]; I ChairmanTree()//初始化 { p[0]=1,pw[0]=Hash(1,1),seed=Hash(233333,456789); for(RI i=1;i<=N;++i) p[i]=(p[i-1]<<1)%X,pw[i]=pw[i-1]*seed; } I int Add(CI k,CI x)//求加上2^x後的和 { RI t=Find(k,x),w=k;x^t&&(Upt0(x,t-1,0,N,w),0);//找到高於或等於當前位的最低的爲0的位後,區間賦值爲0 return Upt1(t,0,N,w),w;//單點賦值爲1 } I bool Less(CI k1,CI k2) {return le(0,N,k1,k2);}//比大小 I int GV(CI l,CI r,CI rt)//求出這個二進制數轉化爲十進制後的值 { if(!rt||l==r) return O[rt].Sz?p[l]:0;RI mid=l+r>>1; return (GV(L)+GV(R))%X; } #undef L #undef R #undef PU }C; int did[N+5]; class Dijkstra//最短路 { private: int vis[N+5],lst[N+5],cnt[N+5],St[N+5]; class SegmentTree//線段樹優化 { private: #define P CI l=1,CI r=n,CI rt=1 #define L l,mid,rt<<1 #define R mid+1,r,rt<<1|1 #define mp make_pair #define fir first #define sec second typedef pair<int,int> Pr;Pr V[N<<2]; I void PU(CI x)//上傳信息 { if(!~V[x<<1].fir) return (void)(V[x]=V[x<<1|1]); if(!~V[x<<1|1].fir) return (void)(V[x]=V[x<<1]); V[x]=V[C.Less(V[x<<1].fir,V[x<<1|1].fir)?x<<1:x<<1|1]; } public: I void Build(P)//建樹,初始化全爲-1 { if(l==r) return (void)(V[rt]=mp(-1,l));RI mid=l+r>>1; Build(L),Build(R),PU(rt); } I void Upt(CI x,CI v,P)//修改 { if(l==r) return (void)(V[rt].fir=v);RI mid=l+r>>1; x<=mid?Upt(x,v,L):Upt(x,v,R),PU(rt); } I int Query() {return ~V[1].fir?V[1].sec:-1;}//詢問最小值 }S; public: I void Solve()//求解最短路 { RI i,k,f,T=0;S.Build(),did[s]=1,S.Upt(s,0); W(~(k=S.Query())) { for(S.Upt(k,-1),i=lnk[k];i;i=e[i].nxt) !vis[e[i].to]&& ( f=C.Add(C.Rt[k],e[i].val),(!did[e[i].to]||C.Less(f,C.Rt[e[i].to]))&& (did[e[i].to]=1,lst[e[i].to]=k,C.Rt[e[i].to]=f,S.Upt(e[i].to,C.Rt[e[i].to]),0) ); } if(!did[t]) puts("-1"),exit(0);k=t;W(St[++T]=k,k^s) k=lst[k];//判無解,求路徑 F.write(C.GV(0,N,C.Rt[t]),'\n'),F.write(T,'\n');W(T) F.write(St[T--],' ');//輸出最終答案 } }D; int main() { RI i,x,y,z;for(F.read(n,m),i=1;i<=m;++i) F.read(x,y,z),add(x,y,z),add(y,x,z);F.read(s,t);//讀入,建邊 return D.Solve(),F.clear(),0; }