[NOI2018] 归程

[NOI2018] 归程

关于 SPFA,它死了。

题意

给定一张 \(n\) 个点,\(m\) 条边的无向连通图。第 \(i\) 条边有长度 \(l\) 和权值 \(a\)。

接下来有 \(q\) 次询问,每个询问给出点 \(v\) 和权值 \(p\)。我们定义点 \(u\) 是好的,当且仅当存在一条 \(u\) 到 \(v\) 的路径中每条边的权值 \(a>p\)。求所有好的节点到 \(1\) 号节点距离的最小值。

多测,强制在线。\(n \leq 2 \times 10^5\),\(m \leq 4 \times 10^5\),\(1 \leq l \leq 10^4\),\(1 \leq a \leq 10^9\).

思路

首先我们需要学习 kruskal 重构树。

对于一张 \(n\) 个点,\(m\) 条边的无向连通图,我们定义其 kruskal 重构树如下:

初始时 kruskal 重构树中有 \(n\) 个节点编号从 \(1\) 到 \(n\)。

接下来跑一遍 kruskal。假设当前要在 \(u\) 和 \(v\) 两点之间连边,边权为 \(w\),则新建一个点,其左儿子为 \(u\) 所在子树的根,右儿子为 \(v\) 所在子树的根,点权为 \(w\)。我们称如此构建的树为 kruskal 重构树。

kruskal 重构树有几个优美的性质。两点在原图中的最小瓶颈路为 kruskal 重构树中两点最近公共祖先点权。kruskal 重构树中的点权具有自顶向下的单调性。

比如在本题中,如果构建出最大生成树的 kruskal 重构树,则在每次询问中,点 \(u\) 是好的当且仅当 kruskal 重构树中 \(u\) 和 \(v\) 的最近公共祖先点权大于 \(p\)。又因为此时构建的 kruskal 重构树中某个点祖先的点权一定不超过自身点权,故所有好的节点都是 kruskal 重构树上某点 \(U\) 所在子树中的叶子。而且 \(U\) 一定是 \(v\) 的某个点权大于 \(p\) 且最靠上的祖先。

所以本题可以先预处理所有点到 \(1\) 的最短路,然后用上面的过程求解即可。

不要用 SPFA。关于 SPFA,它死了。

代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const long long INF=0x3f3f3f3f3f3f3f3f;
const int C=20;
int n,m;
struct Node{
	int v;
	long long w;
};
bool operator <(const Node &lhs,const Node &rhs){
	return lhs.w>rhs.w; 
}
vector<Node> G[200010];
struct Edge{
	int u,v;
	long long w;
}edge[400010];
bool operator <(const Edge &x,const Edge &y){
	return x.w>y.w;
}
long long dis[200010];
bool vis[200010];
void Dijkstra(int u){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[u]=0;
	priority_queue<Node> pq;
	pq.push((Node){u,0});
	while(!pq.empty()){
		u=pq.top().v;
		long long cost=pq.top().w;
		pq.pop();
		if(vis[u]){
			continue;
		}
		vis[u]=true;
		for(int i=0;i<G[u].size();i++){
			int v=G[u][i].v;
			long long w=G[u][i].w;
			if(cost+w<dis[v]){
				dis[v]=cost+w;
				pq.push((Node){v,cost+w});
			}
		}
	}
}
int p[400010],child[400010][2];
long long val[400010];
int Find(int pos){
	if(pos!=p[pos]) p[pos]=Find(p[pos]);
	return p[pos];
}
int root;
void Kruskal(int u){
	for(int i=1;i<=2*n-1;i++){
		p[i]=i;
		child[i][0]=child[i][1]=0;
	}
	val[0]=-INF;
	for(int i=1;i<=n;i++){
		val[i]=INF;
	}
	sort(edge+1,edge+1+m);
	root=n;
	for(int i=1;i<=m;i++){
		int U=Find(edge[i].u),V=Find(edge[i].v);
		if(U!=V){
			root++;
			p[U]=p[V]=root;
			child[root][0]=U;
			child[root][1]=V;
			val[root]=edge[i].w;
		}
	}
}
long long ans[400010];
int pa[400010][C];
void dfs(int u){
	if(u<=n){
		ans[u]=dis[u];
	}
	else{
		int v;
		v=child[u][0];
		pa[v][0]=u;
		for(int i=1;i<C;i++){
			pa[v][i]=pa[pa[v][i-1]][i-1];
		}
		dfs(v);
		v=child[u][1];
		pa[v][0]=u;
		for(int i=1;i<C;i++){
			pa[v][i]=pa[pa[v][i-1]][i-1];
		}
		dfs(v);
		ans[u]=min(ans[child[u][0]],ans[child[u][1]]);
	}
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++){
			G[i].clear();
		}
		for(int i=1;i<=2*n-1;i++){
			for(int j=0;j<C;j++){
				pa[i][j]=0;
			}
		} 
		for(int i=1;i<=m;i++){
			int u,v;
			long long l,a;
			scanf("%d %d %lld %lld",&u,&v,&l,&a);
			G[u].push_back((Node){v,l});
			G[v].push_back((Node){u,l});
			edge[i]=(Edge){u,v,a};
		}
		Dijkstra(1);
		Kruskal(1);
		dfs(root);
		int q,K;
		long long S,lastans=0;
		scanf("%d %d %lld",&q,&K,&S);
		while(q--){
			int v;
			long long p;
			scanf("%d %lld",&v,&p);
			v=(v+K*lastans-1)%n+1;
			p=(p+K*lastans)%(S+1);
			for(int i=C-1;i>=0;i--){
				if(val[pa[v][i]]>p){
					v=pa[v][i];
				}
			}
			printf("%lld\n",ans[v]);
			lastans=ans[v];
		}
	}
	return 0;
}
posted @ 2025-05-31 23:25  Oken喵~  阅读(39)  评论(0)    收藏  举报