【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;
}
posted @ 2020-08-22 08:34  TheLostWeak  阅读(55)  评论(0编辑  收藏