题目大意
%
给出一个
n
n
n 个点,
m
m
m 条边的无向连通图,每条边上有两个参数
l
,
a
l,a
l,a。其中
l
l
l 表示边的长度,
a
a
a 表示边的海拔(即高度)。对于
Q
Q
Q个形如 x p
的询问,有如下定义:
你有一辆车,可以开着它通过任意海拔严格大于
p
p
p 的边,不需要花费任何代价。除此之外,你可以在车开到某个点后,将这辆车彻底报废(这意味着以后都不能再开车),然后通过任意边而不需要考虑其海拔高度,花费为这条边的长度。
你需要回答的是从
x
x
x 号节点开始,开着一辆车,到达节点1需要的最小代价。强制在线。
数据范围
n
∈
[
0
,
2
×
1
0
5
]
∩
Z
,
m
,
q
∈
[
0
,
4
×
1
0
5
]
∩
Z
n\in[0,2\times 10^5]∩\Z,\quad m,q\in[0,4\times 10^5]∩\Z
n∈[0,2×105]∩Z,m,q∈[0,4×105]∩Z
l
∈
[
0
,
2
×
1
0
5
]
∩
Z
,
a
∈
[
0
,
1
0
9
]
∩
Z
l\in[0,2\times 10^5]∩\Z,\quad a\in[0,10^9]∩\Z
l∈[0,2×105]∩Z,a∈[0,109]∩Z
题解
%
什么是kruscal重构树?我们当初在用kruscal求最小生成树的时候,维护连通性用的是并查集,这能十分便捷地判断连通性,但由于路径压缩的缘故,我们完美地丢失了合并的关系,考虑将这种关系建立出来。
当我们在执行算法时,考虑边
⟨
(
u
,
v
)
,
w
⟩
\langle(u,v),w\rangle
⟨(u,v),w⟩,我们新建一个点
P
P
P,然后令
u
u
u 的祖先和
v
v
v 的祖先都认
P
P
P 做祖先,然后将边权存在点
P
P
P 上作为点
P
P
P 的点权。
注意这里的做祖先不是在并查集中执行的,而是要真真实实的建出这样的树来,因此需要再新开一个记录父亲的数组,下图是一个已经建好的例子(最大生成树)。
其中Kruscal的顺序为(1,5),(2,3),(3,5),(1,4),即左侧红色线段。
按照这种方式来建树,可以发现,点数由原来的
n
n
n 个点变为了
2
n
−
1
2n-1
2n−1 个,而且上面有很多优秀的性质。如果不看叶子结点,那么这是一个堆,而且点
u
u
u 到达点
v
v
v 的路径上的边权最大的路径为点
u
u
u 和点
v
v
v 的最近公共祖先的点权。
回到题目中,我们考虑将原图按照海拔建出这样的树,可以发现,对于询问p x
,若点
x
x
x 的其祖先中,最后一个点权大于
p
p
p 的点为
q
q
q,则说明在以
q
q
q 为根的子树内的所有叶子结点均可开车直接到达。
由于一个点到根节点的路径上的点的点券单调不上升,因此对于询问 p x
,我们用倍增求出点
q
q
q,然后求出
q
q
q 子树中那个点距离点1最近即可。这个过程可以用Dijkstra先预处理出1号点到其它点的距离,然后由叶子结点向上合并答案即可。
代码
% 代码经封装,凑合着看。
#include<bits/stdc++.h>
using namespace std;
#define maxn 200010
#define maxm 400010
struct edge{
int u,v,w,next;
bool operator<(const edge &x)const{
return w>x.w;
}
};
int CNT=0;
struct Gha{
edge e[maxm<<1];
int head[maxn],cnt;
Gha(){init();}
void init(){memset(head,cnt=0,sizeof head);}
void ins(int u,int v,int w){
e[++cnt]=(edge){u,v,w,head[u]};
head[u]=cnt;
}
};
struct dijkstra:public Gha{
dijkstra(){}
struct node{
int u,dis;
node(int a,int b):u(a),dis(b){}
bool operator<(const node &x)const{return dis>x.dis;}
};
void work(int s,int *f,int n){//预处理最短路
priority_queue<node> q;
for(int i=1;i<=n;i++) f[i]=0x3f3f3f3fll;
f[s]=0; q.push(node(s,0));
while(!q.empty()){
node x=q.top(); q.pop();
int u=x.u;
if(x.dis!=f[u]) continue;
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
if(e[i].w+f[u]<f[v]){
q.push(node(v,f[v]=e[i].w+f[u]));
}
}
}
}
}_D;
struct kurscal{
edge e[maxm];
int f[maxn<<1],cnt;
kurscal():cnt(0){}
void init(){cnt=0;}
void ins(int u,int v,int w){e[++cnt]=(edge){u,v,w};}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void work(int);
}_K;
int fa[maxn<<1][21],val[maxn<<1];
void kurscal::work(int n){//重构树的过程
int cntv=n;//新的点的编号
for(int i=1;i<=2*n;i++) f[i]=i;
sort(e+1,e+1+cnt);
int done=0;
for(int i=1;i<=cnt&&done<n-1;i++){
int fx=find(e[i].u),fy=find(e[i].v);
if(fx!=fy) fa[fx][0]=fa[fy][0]=f[fx]=f[fy]=++cntv,val[cntv]=e[i].w,++done;
}
}
int dis[maxn<<1];
int n,m;
void prefix(void){
for(int i=n+1;i<=2*n-1;i++) dis[i]=0x3f3f3f3fll;
for(int i=1;i<=2*n-1;i++)//预处理子树到1号点的最短距离
dis[fa[i][0]]=min(dis[fa[i][0]],dis[i]);
for(int j=1;j<=20;j++)//预处理倍增数组
for(int i=1;i<=2*n-1;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int find(int x,int p){//找x的祖先中最后一个val大于p的节点
for(int i=20;i>=0;i--)
if(fa[x][i]&&val[fa[x][i]]>p) x=fa[x][i];
return x;
}
inline char nc() {
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &sum) {
char ch=nc();
int tf=0;
sum=0;
while((ch<'0'||ch>'9')&&(ch!='-'))
ch=nc();
tf=((ch=='-')&&(ch=nc()));
while(ch>='0'&&ch<='9')
sum=sum*10+(ch-48),ch=nc();
(tf)&&(sum=-sum);
}
int main(void){
freopen("return.in","r",stdin);
freopen("return.out","w",stdout);
int T;read(T);
while(T--){
_D.init(); _K.init();//初始化
read(n);read(m);
for(int i=1,u,v,l,a;i<=m;i++){
read(u);read(v);read(l);read(a);
_D.ins(u,v,l);_D.ins(v,u,l);_K.ins(u,v,a);
} _D.work(1,dis,n); _K.work(n); prefix();
int q,k,s; read(q);read(k);read(s);
for(int i=1,v0,p0,lastans=0;i<=q;i++){
read(v0);read(p0);
int v=(v0+k*lastans-1)%n+1;
int p=(p0+k*lastans)%(s+1);
printf("%d\n",lastans=dis[find(v,p)]);
}
}
return 0;
}