题目描述
解法
( ext{Method 1}):( m Kruskal) 重构树
先讲一下 ( m Kruskal) 重构树是个什么东西。
它适用于求解这样的问题:在无向图中,从 (u) 出发只经过边权不超过/不低于阈值 (w) 的边能到达的节点。
举个栗子,假设我们想要求解图中 (u,v) 之间所有路径的最大边权的最小值。先将边权升序排序,对于边 ((u,v,w)),我们查找 (u,v) 连通块的根 (x,y)(这个可以用并查集维护)。如果 (x,y) 不在一个连通块内,建一个虚点 (z),它的点权是 (w),并将其作为 (x,y) 的父亲。这样对于询问 ((u,v)),查询 ( m lca) 的点权即可。
回到这道题。我们以海拔对边降序排序,建出 ( m Kruskal) 重构树。对于询问 ((v,p)),我们找到满足 (val_u>p,val_{fa_u}le p) 的 (u)((u) 是 (v) 的祖先),那么 (u) 子树内的点就是 (v) 可以免费到达的点。可以预处理出 (1) 到每个点的最短路,再维护一下 (u) 子树的点到 (1) 的最短路即可。
复杂度 (mathcal O((m+q)cdot log ncdot T))。
( ext{Method 2}):可持久化并查集
首先可以想出一个离线的做法:将询问海拔降序排序,边也降序排序,每次加入新露出水面的边,和上面一样维护连通块与 (1) 的最短路即可。
我们可以用可持久化并查集维护每种海拔对应的边集的连通块,不过是两个 $log $ 的。
考虑到我们并不需要修改历史版本,可以用另一种方法 —— 半持久化并查集。
说得更明白一点,可持久化并查集是在时间段内查询点,半持久化并查集是在点中查询时间段。将边降序排序,对于每个点用 ( m vector) 存一个类似单调队列的东西,保证海拔递减,但最小值递减,代表的是它的子树贡献的最短路。在连接边 ((u,v,w)) 时,查找 (u,v) 连通块的根 (x,y),若令 (x) 为 (y) 的父亲,那么将 (y) 对应的 ( m vector) 中的最短路插入 (x),因为此时 (y) 对应的 ( m vector) 保存的海拔已经不是瓶颈,而变成了 (w)。答案要求最短路,肯定是插入最优的路径。
询问的时候先跳到没被水淹的深度最低的父亲,再在它的 ( m vector) 里二分即可。这就变成了一个 $log $。
代码
只敲了半持久化并查集(逃
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=2e5+5,maxm=4e5+5;
int n,m,cnt,head[maxn],dis[maxn];
int rk[maxn],val[maxn],f[maxn];
bool vis[maxn];
struct edge {
int nxt,to,w;
} e[maxm<<1];
struct Edge {
int u,v,w,h;
bool operator < (const Edge &t) const {
return h>t.h;
}
} E[maxm];
struct node {
int v,w;
node() {}
node(int V,int W):v(V),w(W) {}
bool operator < (const node &t) const {
return w>t.w;
}
};
priority_queue <node> q;
vector <node> g[maxn];
void addEdge(int u,int v,int w) {
e[++cnt].w=w;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
e[++cnt].w=w;
e[cnt].to=u;
e[cnt].nxt=head[v];
head[v]=cnt;
}
void init() {
cnt=0;
for(int i=1;i<=n;++i)
f[i]=i,head[i]=0,rk[i]=0;
}
void Dijkstra() {
fill(&dis[2],&dis[n]+1,2e9);
fill(&vis[1],&vis[n]+1,0);
q.push(node(1,0));
while(!q.empty()) {
node t=q.top(); q.pop();
if(vis[t.v] or (t.w^dis[t.v]))
continue;
vis[t.v]=1;
for(int i=head[t.v];i;i=e[i].nxt) {
int v=e[i].to;
if(vis[v]) continue;
if(dis[t.v]+e[i].w<dis[v]) {
dis[v]=dis[t.v]+e[i].w;
q.push(node(v,dis[v]));
}
}
}
}
int Find(int x) {
return x==f[x]?x:Find(f[x]);
}
void pushUp(int x,int k,int h) {
if(g[x][g[x].size()-1].w>k)
g[x].push_back(node(h,k));
}
void merge(int x,int y,int h) {
x=Find(x),y=Find(y);
if(x==y) return;
if(rk[x]>rk[y]) swap(x,y);
else if(rk[x]==rk[y]) ++rk[y];
f[x]=y; val[x]=h;
pushUp(y,g[x][g[x].size()-1].w,h);
}
int calc(int x,int h) {
while((x^f[x]) and val[x]>h) x=f[x];
int l=0,r=g[x].size()-1,mid;
while(l<r) {
mid=l+r+1>>1;
if(g[x][mid].v>h)
l=mid;
else r=mid-1;
}
return g[x][l].w;
}
int main() {
for(int T=read(9);T;--T) {
n=read(9),m=read(9);
init();
for(int i=1;i<=m;++i) {
E[i]=(Edge){read(9),read(9),read(9),read(9)};
addEdge(E[i].u,E[i].v,E[i].w);
}
Dijkstra();
for(int i=1;i<=n;++i)
g[i].clear(),
g[i].push_back(node(2e9,dis[i]));
sort(E+1,E+m+1);
for(int i=1;i<=m;++i)
merge(E[i].u,E[i].v,E[i].h);
int q,k,s,lst=0,x,y;
q=read(9),k=read(9),s=read(9);
while(q--) {
x=(read(9)+lst*k-1)%n+1;
y=(read(9)+lst*k)%(s+1);
print(lst=calc(x,y),'
');
}
}
return 0;
}