Kruskal重构树
Kruskal重构树
前言
事情的起因是,上年在上海区域赛中,碰到了铜牌题用到了这个知识点,因为不会,又kuangkuang打铁了。
说要总一下来着,但拖延症发作,硬是拖到了今天,但也不是没有好处,现在由于水平提升一些,我们可以将例题讲的更清楚些。
下面我们就开始吧
实现过程
这点并不难说
- 将边按照从大至小(从小到大)的顺序排序
- 接下来实现Kruskal算法,枚举每条边,若该边两个端点不在一个集合中,则建立一个新点,点权为该边的边权。
- 将边的两个端点与该点放到一个集合中,并且将从新点向两个端点的父节点分别连一条边
至此,Kruskal重构树已经完成架构。
我们来看看图,来更直观的感受一下实现过程

当我们将边权从小到大排序建树时

当我们将边权从大到小排序建树时

到这里,应该算是比较直观了,我们来看看代码实现
其中有一些注意事项
- Kruskal重构树的点数是原图的二倍点数
- 在Kruskal重构树中,所有原点都为叶节点
- 并查集数组,记得初始化二倍原点点数
- 当然,如果原图不连通,我们建出来的是一个森林
const int N = 2e4 + 10,M = 5e4 + 10;//记得点数要开到原图点数的二倍
struct node
{
int u,v,w;
bool operator<(const node& W) const
{
return w>W.w;
}
}edges[M];//边的结构体
int h[N],e[N],ne[N],w[N],idx;//链式向前星用来存数
int p[N];//并查集数组
void add(int a,int b)//加边数组
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int find(int x)//并查集
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
//建树过程
for(int i=1;i<=2*n;i++) h[i] = -1,p[i] = i;//头结点与并查集数组的初始化
for(int i=0;i<m;i++)//将边存起来
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);
edges[i] = {u,v,c};
}
sort(edges,edges+m);//将边排序
int cnt = n;
for(int i=0;i<m;i++)
{
int pa = find(edges[i].u),pb = find(edges[i].v),c = edges[i].w;
if(pa!=pb)
{
w[++cnt] = c;//记得将边权存在新点上
p[pa] = p[pb] = cnt;//将边的两个端点与新点放到同个集合中
add(cnt,pa),add(cnt,pb);//从新点向两个端点的父节点连边
if(cnt==n*2-1) break;
}
}
性质
接下来这部分才是最重要的,我们来看看Kruskal重构树有什么性质
- 若原图不连通,那么建出来的 Kruskal 重构树就是一个森林。
- 如果一开始按照边权升序排序,那么建出的 Kruskal 重构树就是一个大根堆,反之就是小根堆。
- 若一开始按照边权升序排序,那么
lca(u,v)的权值代表了原图中 u 到 v 路径上最大边权的最小值。反之就是最小边权的最大值。 - Kruskal 重构树中的叶子结点必定是原图中的节点,其余的节点都是原图的一条边。
- Kruskal 重构树建好以后会比原图多出 n-1个节点(如果原图联通的话)
基本上,在注意事项中,我们都有提到过
我们主要证明一下第三条,若一开始按照边权升序排序,那么 lca(u,v) 的权值代表了原图中 u 到 v 路径上最大边权的最小值。反之就是最小边权的最大值。
我们,证明若按照升序排列,则LCA代表的是原图中u到v的路径的最大边权的最小值。
首先,我们简单证明一下第二条,按照升序排列,父节点均比其子节点大,则为一个大根堆。
若按照升序排列,则从叶节点向上走时,我们都是尽量沿着边权小的值走的,因此深度较小的LCA则为u->v中边权最大的值。同理我们可以证明降序排列。
应用
说了这么多,但其实应用才是我们最关注的问题。
u->v的路径中,最大边权的最小值/最小边权的最大值
这个用法就是我们性质的最直接运用,这里跟两道例题
货车运输
看到题目,要我们求的就是,最小边权的最大值了,直接求一下LCA即可。
也给我们一个提示,遇到这种最大值最小或者最小值最大这种类似的语句,可以不急着想二分,还可以想想 Kruskal 重构树。
至于求LCA,可以用树剖,也可以用倍增法,看你的喜欢。
一般因为树剖用的空间更小,一般我喜欢用树剖。但是!!倍增也可以的,不要因为不会树剖就不写了,倍增也是有倍增才能干的事。
解法
- 将边权降序排列,建出Kruskal重构树,可能是森林,注意标记一下
- 接下里就求LCA即可
当然,因为这是Kruskal重构树的讲解,但我们也简单讲一下另一个写法。
- 我们建立原图的最大生成树(用Kruskal的时候,将边权降序排列)
- 接下来对建立出来的最大生成树用倍增法求LCA时,我们额外预处理一个数组
mmin,它维护的是倍增的路径中的边权最小值 - 接下来求LCA的时候,就直接用倍增法,顺便维护一下路径中的边权最小值即可
两个代码,我都会贴一下
Kruskal重构树
#include<bits/stdc++.h>
using namespace std;
const int N = 6e4 + 10;
struct Edge
{
int u,v,w;
bool operator<(const Edge& W)const
{
return w>W.w;
}
}edges[N];
int h[N],e[N],ne[N],w[N],idx;
int p[N];
bool st[N];
int sz[N],fa[N],son[N],dep[N];
int top[N];
int n,m;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int find(int x)
{
if(x!=p[x]) p[x] = find(p[x]);
return p[x];
}
void dfs1(int u,int pa,int depth)
{
st[u] = 1,sz[u] = 1,fa[u] = pa,dep[u] = depth;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==pa) continue;
dfs1(j,u,depth+1);
sz[u] += sz[j];
if(sz[j]>sz[son[u]]) son[u] = j;
}
}
void dfs2(int u,int tp)
{
top[u] = tp;
if(!son[u]) return ;
dfs2(son[u],tp);
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==son[u]||j==fa[u]) continue;
dfs2(j,j);
}
}
int LCA(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u = fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
edges[i] = {u,v,c};
}
sort(edges+1,edges+m+1);
for(int i=1;i<=n;i++) p[i] = i;
for(int i=1;i<=m;i++)
{
int u = edges[i].u,v = edges[i].v,c = edges[i].w;
int pa = find(u),pb = find(v);
if(pa!=pb)
{
w[++n] = c;
p[n] = p[pa] = p[pb] = n;
add(n,pa);
add(n,pb);
}
}
for(int i=1;i<=n;i++)
if(!st[i]){
int f = find(i);
dfs1(f,-1,1),dfs2(f,f);
}
int q;scanf("%d",&q);
while(q--)
{
int u,v;scanf("%d%d",&u,&v);
int pa = find(u),pb = find(v);
if(pa!=pb) puts("-1");
else printf("%d\n",w[LCA(u,v)]);
}
return 0;
}
最大生成树+倍增
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10,M = 2*N;
struct node
{
int a,b,w;
bool used;
bool operator<(const node &W) const{
return w>W.w;
}
}edges[50010];
int h[N],e[M],ne[M],w[M],idx;
int d[N],fa[N][16],dmin[N][16],p[N];
bool st[N];
int n,m;
template < typename T >
inline void read(T &x)
{
x = 0; bool f = 0; char ch = getchar();
while(!isdigit(ch)){f ^= !(ch ^ 45);ch=getchar();}
while(isdigit(ch)) x= (x<<1)+(x<<3)+(ch&15),ch=getchar();
x = f ? -x : x;
}
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void bfs(int root)
{
d[root]=1;
queue<int> q;
q.push(root);
while(q.size())
{
int t = q.front();
q.pop();
for(int i=h[t];~i;i=ne[i])
{
int j = e[i];
if(d[j]>d[t]+1)
{
q.push(j);
d[j]=d[t]+1;
fa[j][0]=t;
dmin[j][0]=w[i];
for(int k=1;k<=15;k++)
{
int anc = fa[j][k-1];
fa[j][k]=fa[anc][k-1];
dmin[j][k]=min(dmin[j][k-1],dmin[anc][k-1]);
}
}
}
}
}
int lca(int a,int b)
{
int ans = 0x3f3f3f3f;
if(d[a]<d[b]) swap(a,b);
for(int i=15;i>=0;i--)
if(d[fa[a][i]]>=d[b])
{
ans = min(dmin[a][i],ans);
a=fa[a][i];
}
if(a==b) return ans;
for(int i=15;i>=0;i--)
if(fa[a][i]!=fa[b][i])
{
ans = min(ans,dmin[a][i]);
ans = min(ans,dmin[b][i]);
a=fa[a][i];
b=fa[b][i];
}
ans = min(min(dmin[a][0],dmin[b][0]),ans);
return ans;
}
int main()
{
read(n),read(m);
for(int i=0;i<m;i++)
{
int a,b,c;
read(a),read(b),read(c);
edges[i]={a,b,c,0};
}
sort(edges,edges+m);
for(int i=1;i<=n;i++)
{
h[i]=-1;
p[i]=i;
}
for(int i=0;i<m;i++)
{
int a = edges[i].a,b = edges[i].b;
int pa=find(a),pb=find(b);
if(pa!=pb)
{
p[pa]=pb;
edges[i].used=1;
}
}
int cnt = 0;
for(int i=0;i<m;i++)
if(edges[i].used){
add(edges[i].a,edges[i].b,edges[i].w);
add(edges[i].b,edges[i].a,edges[i].w);
}
memset(d,0x3f,sizeof d);
memset(dmin,0x3f,sizeof dmin);
d[0]=0;
for(int i=1;i<=n;i++){
int x = find(i);
if(!st[x]){
st[x]=1;
bfs(x);
}
}
int q;
read(q);
while(q--)
{
int x,y;
read(x),read(y);
int pa = find(x),pb = find(y);
if(pa!=pb) puts("-1");
else printf("%d\n",lca(x,y));
}
return 0;
}
牛客练习赛62——水灾
几乎与货车运输一模一样,我们依旧要求最小边权的最大值
但是略有不同的是,这次我们要知道的是一堆点之间的两两之间的最小边权的最大值
很明显,我们不能直接按给定顺序对所有点求LCA,因为可能有两个点的LCA深度很大,即最小边权的最大值很大,但是按给定的编号顺序求,可能会将这个LCA忽略掉。
也不难想,我们只需要按照dfs序从小到大去对所有点求LCA,就不会漏掉答案了,因为dfs序相邻的,其在树中也一定相邻,则求出的LCA深度会更深。并且从小到大求LCA,dfs序小的节点其深度也深。因此其包含了两两互相求LCA的所有情况。
其余都与上一题类似。直接看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10,M = 5e5 + 10;
struct Edge
{
int u,v,w;
bool operator<(const Edge &W) const
{
return w>W.w;
}
}edges[M];
int h[N],e[N],ne[N],w[N],idx;
int sz[N],dep[N],fa[N],son[N];
int top[N],p[N],id[N],d[N],ts;
int n,m,q;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
void dfs1(int u,int pa,int depth)
{
sz[u] = 1,fa[u] = pa,dep[u] = depth;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
dfs1(j,u,depth+1);
sz[u] += sz[j];
if(sz[j]>sz[son[u]]) son[u] = j;
}
}
void dfs2(int u,int tp)
{
top[u] = tp,id[u] = ++ts;
if(!son[u]) return ;
dfs2(son[u],tp);
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==son[u]) continue;
dfs2(j,j);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u = fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
bool cmp(int i,int j)
{
return id[i]<id[j];
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);
edges[i] = {u,v,c};
}
sort(edges+1,edges+m+1);
for(int i=1;i<=2*n;i++) p[i] = i;
int cnt = n;
for(int i=1;i<=m;i++)
{
int u = edges[i].u,v = edges[i].v,c = edges[i].w;
int pa = find(u),pb = find(v);
if(pa!=pb)
{
w[++cnt] = c;
p[pa] = p[pb] = cnt;
add(cnt,pa),add(cnt,pb);
if(cnt==n*2-1) break;
}
}
dfs1(cnt,-1,1),dfs2(cnt,cnt);
int ans = 0;
while(q--)
{
int k;scanf("%d",&k);
int now = 0;
for(int i=0;i<k;i++) scanf("%d",d+i),d[i]^=ans;
sort(d,d+k,cmp);
ans = 0;
for(int i=1;i<k;i++)
ans = max(ans,w[lca(d[i-1],d[i])]);
printf("%d\n",ans);
}
return 0;
}
从 u 出发只经过边权不超过 (不低于于)x 的边能到达的节点
根据性质,可以发现,只需要找到边权升序(降序)的 Kruskal 重构树中找到深度最小的,点权不超过 (不低于)x 的节点,那么这个节点的子树即为所求。
因为若能到达该节点,则可以到达该节点的所有叶节点
找这个点一般用树上倍增,这就是我说的有些题目只有倍增可做,因为倍增可以准确的找到这个点,而树剖并不可以。
这里有三道例题,难度逐渐递增
46届ICPC上海H——Life is a Game
这是这届的铜牌题,也是这种应用中比较简单的一种体现。
简单分析题目后,我们发现,我们要求的就是
从给定的起点出发,我们想过一条边,需要我们的能力值超过边权,每到达一个点后,我们现在的所拥有的能力值为初始值+该节点下的子树的所有权值。
我们最后归纳一下,即为求从u出发,只经过不超过自身能力值的边所能达到的最高节点,答案即为初始值+该点下的所有节点能给的能力值
解法很简单
- 先将边权升序排列,接下来建立Kruskal重构树。
- 初始倍增数组
fa[N][20],并且计算出,树中每个节点下面子树的能给予的能力值的和(只有初始的n个点有值) - 对于所有的询问,我们只需要从给定的起点出发不断递归到答案即可。
需要强调一下的事情是
我们对我们的哨兵加一个特判,我们的哨兵是0号点,而零号点的权值为0,若不加特判,则哨兵也符合我们的条件,则会死循环
因此,当我们判断的是不超过的时候,我们应该判断一下倍增过去的点一定是大于0的
看看代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10,M = 1e5 + 10;
struct Edge
{
int u,v,w;
bool operator<(const Edge& W)const
{
return w<W.w;
}
}edges[M];
int h[N],e[N],ne[N],w[N],idx;
int fa[N][20],sum[N],a[N],p[N];
int n,m,q;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
void dfs(int u)
{
if(u<=n) sum[u] = a[u];
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
fa[j][0] = u;
for(int k=1;k<=19;k++) fa[j][k] = fa[fa[j][k-1]][k-1];
dfs(j);
sum[u] += sum[j];
}
}
int get(int u,int x,int res)
{
if(!fa[u][0]) return x + res;
if(w[fa[u][0]]>x+res) return x+res;
for(int i=19;i>=0;i--)
if(fa[u][i]&&x+res>=w[fa[u][i]])
u = fa[u][i];
return get(u,x,sum[u]);
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=2*n;i++) h[i] = -1,p[i] = i;
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=0;i<m;i++)
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);
edges[i] = {u,v,c};
}
sort(edges,edges+m);
int cnt = n;
for(int i=0;i<m;i++)
{
int pa = find(edges[i].u),pb = find(edges[i].v),c = edges[i].w;
if(pa!=pb)
{
w[++cnt] = c;
p[pa] = p[pb] = cnt;
add(cnt,pa),add(cnt,pb);
if(cnt==n*2-1) break;
}
}
dfs(2*n-1);
while(q--)
{
int x,k;scanf("%d%d",&x,&k);
printf("%d\n",get(x,k,a[x]));
}
return 0;
}
P4768 [NOI2018] 归程
这题就要比上一题复杂的一些,但思路依旧是极其相似,我们来模仿上一题进行归纳。
我们要求的就是
我们要求的是只经过海拔不低于x的边,能到达的所有点中,到达1号点的最短路径
因此,我们的步骤如下
- 首先先从1号点跑出到其余所有点的最短路
- 将边权降序排列,建立Kruskal重构树
- 接下来,从重构树的根节点,跑一个树形DP,跑出树中所有点到1号点的最短路。并且预处理出来倍增数组
- 最后,对于每次询问,我们从v跑出答案即可
我们,来看看代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 4e5 + 10,M = N<<1,INF = 0x3f3f3f3f;
struct Edge
{
int u,v,w;
bool operator<(const Edge& W)const
{
return w>W.w;
}
}edges[N];
int h[N],e[M],ne[M],w[M],idx;
int f[N],fa[N][20],p[N];
bool st[N];
int n,m,q,k,s;
void add(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)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
void dijkstra()
{
priority_queue<PII,vector<PII>,greater<PII>> q;
q.push({0,1});
f[1] = 0;
while(q.size())
{
auto t = q.top();
q.pop();
int ver = t.second;
if(st[ver]) continue;
st[ver] = 1;
for(int i=h[ver];~i;i=ne[i])
{
int j = e[i];
if(f[j]>f[ver]+w[i])
{
f[j] = f[ver] + w[i];
q.push({f[j],j});
}
}
}
}
void dfs(int u)
{
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
fa[j][0] = u;
for(int k=1;k<=19;k++) fa[j][k] = fa[fa[j][k-1]][k-1];
dfs(j);
f[u] = min(f[u],f[j]);
}
}
int get(int u,int p)
{
for(int i=19;i>=0;i--)
if(w[fa[u][i]]>p)
u = fa[u][i];
return u;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
idx = 0;
for(int i=0;i<=2*n;i++)
{
f[i] = INF;
h[i] = -1;
p[i] = i;
st[i] = 0;
}
for(int i=0;i<m;i++)
{
int u,v,l,a;
scanf("%d%d%d%d",&u,&v,&l,&a);
add(u,v,l),add(v,u,l);
edges[i] = {u,v,a};
}
dijkstra();
sort(edges,edges+m);
for(int i=0;i<=2*n;i++) h[i] = -1,w[i] = 0;
idx = 0;
int cnt = n;
for(int i=0;i<m;i++)
{
int u = edges[i].u,v = edges[i].v,c = edges[i].w;
int pa = find(u),pb = find(v);
if(pa!=pb)
{
w[++cnt] = c;
p[pa] = p[pb] = cnt;
add(cnt,pa),add(cnt,pb);
if(cnt==2*n-1) break;
}
}
dfs(find(1));
scanf("%d%d%d",&q,&k,&s);
int lastans = 0;
while(q--)
{
int v0,p0;scanf("%d%d",&v0,&p0);
int v = (v0+lastans*k-1)%n+1,p = (p0+k*lastans)%(s+1);
printf("%d\n",lastans = f[get(v,p)]);
}
}
return 0;
}
P7834 [ONTAK2010] Peaks 加强版
这题,题意说的很明白了
求从 u 开始只经过权值 小于等于x 的边所能到达的权值第 k 大的点的权值,如果不存在输出 -1。
我们知道,只经过不超过x的边,所能到达的所有原图中的点,即为我们在重构树中跑出符合限制的节点的子树的叶节点。
那我们需要操作的即为对于重构树中跑出的节点的子树中的叶节点中权值第k的点的权值
不难想到,求权值第k大,我们可以用主席树。
同时,一个子树中所有的叶节点编号,我没想让它们连续并不难办。
我们只需要对dfn与sz数组稍作改变。使dfn[u]只记录叶节点u的编号,sz[u]只记录对应节点u下的叶节点数量。
并且,我们增加两个辅助数组f,tk,其中f[u]用来记录,在子树中最小的叶节点编号,而tk则是桶排数组,其记录的是dfs序所对应的节点编号。
我们可以发现f与dfn数组可以合为一个。
因此,当我们找到符合限制的节点后,其间的叶节点的编号范围为[f[u],f[u]+sz[u]-1]
我们再利用,主席树即可求出区间第k大
嗷,千万别忘记离散化了。
我们来看看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,M = 5e5 + 10,INF = 0x3f3f3f3f;
struct Node
{
int l,r;
int cnt;
}tr[N*4+N*17];//主席树结构体
struct Edge
{
int u,v,w;
bool operator<(const Edge& W)const
{
return w<W.w;
}
}edges[M];//边的结构体
int h[N<<1],e[N<<1],ne[N<<1],w[N<<1],idx;//用来存树
int a[N],p[N<<1];//a是原图节点的权值,p为重构树中各节点编号的并查集数组
int fa[N<<1][20],f[N<<1],sz[N<<1],ts;//倍增数组与辅助数组
int root[N],id,tk[N];//主席树版本数组与桶排数组
vector<int> nums;//离散化后的数组
int n,m,Q;
void add(int a,int b)//加边
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int find(int x)//并查集
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int get(int x)//获取a的离散化后的下标
{
return lower_bound(nums.begin(),nums.end(),x) - nums.begin();
}
int get(int u,int x)//获取符合限制的节点编号
{
for(int i=19;i>=0;i--)
if(fa[u][i]&&w[fa[u][i]]<=x)//记得为哨兵节点加一个特判
u = fa[u][i];
return u;
}
void dfs(int u)//对倍增数组的初始化,并对f,sz,tk数组初始化
{
if(u<=n) f[u] = ++ts,sz[u] = 1,tk[ts] = u;
else f[u] = INF;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
fa[j][0] = u;
for(int k=1;k<=19;k++) fa[j][k] = fa[fa[j][k-1]][k-1];
dfs(j);
f[u] = min(f[u],f[j]);
sz[u] += sz[j];
}
}
int insert(int p,int l,int r,int x)//建立不同版本的主席树
{
int q = ++id;
tr[q] = tr[p];
if(l==r)
{
tr[q].cnt++;
return q;
}
int mid = l + r >> 1;
if(x<=mid) tr[q].l = insert(tr[p].l,l,mid,x);
else tr[q].r = insert(tr[p].r,mid+1,r,x);
tr[q].cnt=tr[tr[q].l].cnt+tr[tr[q].r].cnt;
return q;
}
int query(int q,int p,int l,int r,int k)//查询区间第k小(我们将求的第k大做个转化即可)
{
if(l==r) return l;
int mid = l + r >> 1;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
if(k<=cnt) return query(tr[q].l,tr[p].l,l,mid,k);
else return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=2*n;i++) p[i] = i,h[i] = -1;//初始化
for(int i=1;i<=n;i++) scanf("%d",a+i),nums.push_back(a[i]);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());//离散化
for(int i=0;i<m;i++)
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);
edges[i] = {u,v,c};
}
sort(edges,edges+m);
int t = n;
for(int i=0;i<m;i++)//建树
{
int u = edges[i].u,v = edges[i].v,c = edges[i].w;
int pa = find(u),pb = find(v);
if(pa!=pb)
{
w[++t] = c;
p[pa] = p[pb] = t;
add(t,pa),add(t,pb);
if(t==2*n-1) break;
}
}
dfs(t);
for(int i=1;i<=n;i++)//建立主席树
root[i] = insert(root[i-1],0,nums.size()-1,get(a[tk[i]]));
int lastans = 0;
while(Q--)
{
int u0,x0,k0;scanf("%d%d%d",&u0,&x0,&k0);
int u = (u0^lastans)%n+1,k = (k0^lastans)%n+1,x = x0^lastans;
int ii = get(u,x);
if(sz[ii]<k) puts("-1"),lastans = 0;//判断子树中是否有k个叶节点
else printf("%d\n",lastans = nums[query(root[f[ii]+sz[ii]-1],root[f[ii]-1],0,nums.size()-1,sz[ii]-k+1)]);//将第k大,转化为求sz[ii]-k+1小
}
}

浙公网安备 33010602011771号