这个算法是因为借用了kruscal合并并查集集合维护连通性,并且重建树不破坏结构得名。
具体的思想是:
我们按照边权排序(本题从大到小排序)
我们在不断合并集合的同时,每次合并两个集合的祖先,都建立一个虚拟原点,使得这个点指向两个祖先,并且边权等于点权。这就是kruscal重构树,本质上是一个二叉堆。
这样做的好处是,树上的点权都是符合单调性的,可以解决一类只能经过大于指定长度的边的图论问题。我们只需要倍增跳跃,满足条件的子树都是互通的。
对于这一题,先用最短路维护答案,建树后,dfs就能求得满足边不小于指定长度的集合的到终点的最小距离。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=1e6+10; const int M=2e6+10; const int inf=0x3f3f3f3f; int n,m,Q,K,S; int f[N][22],h[N],ne[N],e[N],idx; int h1[N],ne1[N],e1[N],num; ll last,d[N]; int p[N],depth[N],w[N],st[N]; struct node{ ll a,b,c,d;//c:长度 d:海拔 }tr[N],ans[N]; void add1(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } void add(int a,int b){ e1[num]=b,ne1[num]=h1[a],h1[a]=num++; } void dij(){ memset(d,0x3f,sizeof d); memset(st,0,sizeof st); d[1]=0; priority_queue<pll,vector<pll>,greater<pll>> q; q.push({d[1],1}); while(q.size()){ auto t=q.top(); q.pop(); if(st[t.second]) continue; st[t.second]=1; for(int i=h[t.second];i!=-1;i=ne[i]){ int j=e[i]; if(d[j]>d[t.second]+w[i]){ d[j]=d[t.second]+w[i]; q.push({d[j],j}); } } } for(int i=1;i<=n;i++) ans[i].c=d[i]; } void init(){ num=idx=last=0; memset(f,0,sizeof f); memset(h1,-1,sizeof h1); memset(h,-1,sizeof h); } int find(int x){ if(p[x]!=x){ p[x]=find(p[x]); } return p[x]; } bool cmp(node a,node b){ return a.d>b.d; } void dfs(int u,int pa){//求取krusacl重构树上的最小值,因为子树上的点可以互相到达 depth[u]=depth[pa]+1; f[u][0]=pa; int i; for(i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1]; for(i=h1[u];i!=-1;i=ne1[i]){ int j=e1[i]; dfs(j,u); ans[u].c=min(ans[u].c,ans[j].c); } } ll query(int x,int y){//倍增跳到最上面满足条件的点 for(int i=20;i>=0;--i) if(depth[x]-(1<<i)>0&&ans[f[x][i]].d>y) x=f[x][i]; return ans[x].c; } void solve(){ int i; for(i=1;i<=2*n;i++) p[i]=i; sort(tr+1,tr+1+m,cmp); int tot=0,cnt=n; for(i=1;i<=m;i++){//建立虚拟点,并用点权代替边权 int pa=find(tr[i].a); int pb=find(tr[i].b); if(pa!=pb){ add(++cnt,pa);//因为并查集破坏结构因此重建树 add(cnt,pb); p[pa]=cnt; p[pb]=cnt; ans[cnt].d=tr[i].d; tot++; } if(tot==n-1) break; } dfs(cnt,0); while(Q--){ int u,v; cin>>u>>v; int x=(K*last+u-1)%n+1,y=(K*last+v)%(S+1); cout<<(last=query(x,y))<<endl; } } int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ cin>>n>>m; init(); int i; for(i=1;i<=m;i++){ int a,b,c,d; cin>>a>>b>>c>>d; tr[i]={a,b,c,d};//kruscal存边 add1(a,b,c); add1(b,a,c); } for(i=n+1;i<=n*2;i++) ans[i].c=0x3f3f3f3f;//初始点不存在的点先赋值为极大值 dij(); cin>>Q>>K>>S; solve(); } }