[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喵~  阅读(6)  评论(0)    收藏  举报