【LOJ2718】「NOI2018」归程(Kruskal重构树)
大致题意: 给定一个无向图,每条边有一个长度以及一个海拔。多组询问,每次给定起点以及一个限制\(h\),要求从起点出发,先开车走海拔大于等于\(h\)的边到达某一节点,然后步行到达\(1\)号点。求最短的步行路程。
关于\(SPFA\),它已经死了
\(Kruskal\)重构树
对于这种题目,我们首先以海拔为关键字建出\(Kruskal\)重构树,然后有一个重要结论:
从\(x\)出发只经过边权大于等于/小于等于\(v\)的边所能到达的点集,就是\(x\)深度最小的点权大于等于\(v\)/小于等于\(v\)的祖先子树内所有的叶节点。
实际求解时可以利用中点权的单调性倍增上跳\(O(logn)\)求出这个祖先。
解题思路
根据先前提到的重要结论,我们可以\(O(logn)\)求出从起点出发开车能到达的点集,而答案就是这些点到\(1\)号点的最短路的最小值。
而每个点到\(1\)号点的最短路可以事先\(Dijkstra\)预处理。
只要对于\(Kruskal\)重构树上的每个点维护一下子树内叶节点最短路的最小值即可。
代码
#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 200000
#define M 400000
#define LN 20
#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,ee,lnk[N+5];struct edge {int to,nxt,val;}e[2*M+5];
struct line {int x,y,h;I bool operator < (Con line& o) Con {return h>o.h;}}s[M+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,E=(C=FO)+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
namespace DJ//Dijkstra预处理最短路
{
#define mp make_pair
#define fi first
#define se second
typedef pair<int,int> Pr;priority_queue<Pr,vector<Pr>,greater<Pr> > q;
int dis[N+5],vis[N+5];I void Dijkstra()
{
RI i,t;Pr k;for(i=1;i<=n;++i) dis[i]=2e9,vis[i]=0;q.push(mp(dis[1]=0,1));W(!q.empty())
{
if(k=q.top(),q.pop(),vis[k.se]) continue;
for(vis[k.se]=1,i=lnk[k.se];i;i=e[i].nxt)
(t=dis[k.se]+e[i].val)<dis[e[i].to]&&(q.push(mp(dis[e[i].to]=t,e[i].to)),0);
}
}
}
class KruskalTree//Kruskal重构树
{
private:
int rt,V[N<<1],H[N<<1],S[N<<1][2],f[N<<1][LN+5];
int fa[N<<1];I int getfa(CI x) {return fa[x]^x?fa[x]=getfa(fa[x]):x;}//并查集
I void Init(CI x)//预处理
{
RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];if(x<=n) return (void)(V[x]=DJ::dis[x]);//叶节点直接赋权
f[S[x][0]][0]=f[S[x][1]][0]=x,Init(S[x][0]),Init(S[x][1]),V[x]=min(V[S[x][0]],V[S[x][1]]);//递归子树,上传信息
}
I int Jump(RI x,CI v) {for(RI i=LN;~i;--i) H[f[x][i]]>v&&(x=f[x][i]);return x;}//倍增上跳
public:
I void Build(line *s)//Kruskal算法
{
RI i,x,y;for(i=1;i<=2*n-1;++i) fa[i]=i;for(sort(s+1,s+m+1),rt=n,i=1;i<=m;++i)
(x=getfa(s[i].x))^(y=getfa(s[i].y))&&(H[++rt]=s[i].h,fa[S[rt][0]=x]=fa[S[rt][1]=y]=rt);//建树
f[rt][0]=0,Init(rt);
}
I int Q(CI x,CI v) {return V[Jump(x,v)];}//询问,返回子树内叶节点最短路的最小值
}K;
int main()
{
RI Tt,Qt,i,x,v,op,Mx,lst;F.read(Tt);W(Tt--)
{
for(F.read(n),F.read(m),ee=0,i=1;i<=n;++i) lnk[i]=0;//清空
for(i=1;i<=m;++i) F.read(s[i].x),F.read(s[i].y),
F.read(v),F.read(s[i].h),add(s[i].x,s[i].y,v),add(s[i].y,s[i].x,v);
DJ::Dijkstra(),K.Build(s);//预处理
F.read(Qt),F.read(op),F.read(Mx),lst=0;W(Qt--)
F.read(x),F.read(v),op&&(x=(x+lst-1)%n+1,v=(v+lst)%(Mx+1)),F.writeln(lst=K.Q(x,v));//询问
}return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒