D148 Kruskal 重构树 P4768 [NOI2018] 归程

D148 Kruskal 重构树 P4768 [NOI2018] 归程_哔哩哔哩_bilibili

 

P4768 [NOI2018] 归程 - 洛谷

无向图,n 个点,m 条边,每条边有两个边权 长度 w 和 海拔 hi,q 次询问,每次询问给一个起点 v 和一个当天水位 p,

要求从 v 点回到 1 号点,只有当前边的海拔 hi 大于水位 p 才可以开车,一旦不满足条件必须步行,求步行的最小距离。

思路

如果 $v$ 到 $1$ 的最短路上的边都满足海拔 $h_i>p$,那么答案就是最短路

先跑 Dijkstra,预处理出 1 号点到每个点的最短路,再考虑海拔的约束条件

如果不满足条件,就要找出从 $v$ 出发只经过海拔 $h_i>p$ 的边能到达的哪些节点,怎样找?

Kruskal 重构树可以求原图上从 $x$ 出发只经过边权 $\ge val$ 的边能到达的节点数,并且这些点都在以某个新建点为根的子树中

我们以海拔为边权,先建 Kruskal 重构树,然后从 v 点开始,树上倍增,找到深度最小且 $h_i>p$ 的根节点

怎样求该节点到 1 号点的距离?

提前遍历一遍重构树,从根节点进入,自底向上更新,预处理出每个新建点到 1 号点的距离

图中,节点 $5,6,7$ 到 $1$ 号点的距离分别为 $1,0,0$。从 $4$ 出发,找海拔大于 $2$ 的点就是节点 $5$,$5$ 到 $1$ 号点的距离是 $1$

相关板子:

D146【模板】Kruskal 重构树 U201076 [Bzoj3732] Network - 董晓 - 博客园

D09【模板】最近公共祖先 倍增算法 - 董晓 - 博客园

 

// Kruskal 重构树 O(MlogM+NlogN)
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;

int read(){
  int f=1,x=0; char c=getchar();
  while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
  return f*x;
}
const int N=800005;
int idx,h[N],to[N],ne[N],ww[N]; //边权为长度
void add(int u,int v,int w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
struct E{int u,v,hi;}e[N]; //边权为海拔
int n,m,cnt,val[N]; //val新建点点权
int d[N],vis[N];
int pa[N],fa[N][20];

void dijkstra(){ //预处理1到所有节点的最短路
  memset(vis,0,sizeof vis);
  memset(d,0x3f,sizeof d); d[1]=0;
  priority_queue<pii,vector<pii>,greater<pii> > q; //小根堆
  q.push({0,1});
  while(!q.empty()){
    auto u=q.top().second; q.pop();
    if(vis[u])continue; vis[u]=1;
    for(int i=h[u];i;i=ne[i]){
      int v=to[i],w=ww[i];
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        q.push({d[v],v});
      }
    }
  }
}
int find(int x){ //并查集查找
  return pa[x]==x?x:pa[x]=find(pa[x]);
}
void kruskal(){ //重构树
  memset(h,0,sizeof(h)); idx=1;
  sort(e+1,e+1+m,[&](E a,E b){return a.hi>b.hi;});
  for(int i=1;i<=n;++i)pa[i]=i; 
  for(int i=1;i<=m;i++){
    int fu=find(e[i].u), fv=find(e[i].v);
    if(fu!=fv){
      val[++cnt]=e[i].hi;
      pa[fu]=pa[fv]=pa[cnt]=cnt;
      add(cnt,fu,0); add(cnt,fv,0);
    }
  }
}
void dfs(int u,int f){ //预处理 d,fa
  fa[u][0]=f;
  for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    dfs(v,u);
    d[u]=min(d[u],d[v]); //更新那些新建点到1的最短距离
  }
}
int main(){
  int T=read();
  while(T--){
    memset(h,0,sizeof(h)); idx=1;
    memset(fa,0,sizeof(fa)); 
    memset(d,0x3f,sizeof(d));
    
    n=read();m=read();cnt=n; //cnt新建点
    for(int i=1;i<=m;i++){
      int u=read(),v=read(),w=read(),hi=read();
      add(u,v,w); add(v,u,w);
      e[i]={u,v,hi};
    }
    dijkstra(); //预处理1到所有节点的最短路
    kruskal();  //重构树
    dfs(cnt,0); //预处理 d,fa

    int Q=read(),K=read(),S=read();
    for(int last=0;Q--;){
      int v=read(),p=read();
      v=(v+K*last-1)%n+1;
      p=(p+K*last)%(S+1);
      for(int j=19;j>=0;--j) //倍增找到深度最小且海拔大于水位的节点
        if(fa[v][j] && val[fa[v][j]]>p) v=fa[v][j];
      printf("%lld\n",last=d[v]);
    }
  }
}

 

posted @ 2026-04-28 22:44  董晓  阅读(116)  评论(0)    收藏  举报