【BZOJ3545/BZOJ3551】Peaks(ONTAK2010)-Kruskal重构树+主席树
测试地址:Peaks原版,Peaks加强版
题目大意:一张个点条边的无向图,点和边都有权,个询问,每次询问从一个点能仅通过走边权不超过的边走到的所有点中,第大的点权。原版不强制在线,加强版强制在线。
做法:本题需要用到Kruskal重构树+主席树。
注意到两点间路径最大值的最小值一定在最小生成树上,因此只有最小生成树上的边是有用的。
在原版题中,不强制在线,我们就可以把询问按从小到大排序,用Kruskal算法和平衡树启发式合并即可处理这些询问,时间复杂度为。
而在加强版中,强制在线使得我们不能使用上面的方法。注意到,上面的方法非常依赖于加边的顺序,那么有没有一种方法来描述Kruskal算法的过程,使得我们可以脱离时序进行询问呢?答案是肯定的,这时候就要引出Kruskal重构树这个东西了。
建树的思想其实非常简单,在Kruskal算法中,每往最小生成树中加一条边,就新增一个点表示这条边,并向它连接的两个点所属的树的顶部连边,这样我们就建出了这棵树。这棵树有一些重要的性质:
1.所有叶子节点都是原图中的点,其他点都为边。
2.一个点所表示的边的边权,一定不比它儿子表示的边的边权小。
3.两个叶子节点的LCA所表示的边的边权,为原图最小生成树中两点间的最大边权。
这些性质都是很显然成立的,于是我们很自然地想到一个做法:
利用性质2,每次询问,我们可以从点向上倍增到最顶部的不大于的点,那么以这个点为根的子树内所有叶子节点,就是从点能经过不超过的边权到达的点了,而询问一棵子树中的第大值,立马想到在DFS序上用主席树维护,这样我们就解决了这一题,时间复杂度为。
(然而非常有毒的一点是,以下这份代码在原版题中A了,而加了注释中的解密语句后在加强版题中RE了,而该题RE数比其他结果的数量都多得多……有待弄清情况)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,q,tot,totp,pos[200010],h[200010];
int fa[200010][20]={0},ch[200010][2]={0},tim;
int top[200010],topp[200010],siz[200010]={0},in[200010],out[200010];
int rt[200010]={0},T=0,seg[2000010]={0},segch[2000010][2]={0},pp[100010];
bool vis[200010]={0};
struct edge
{
int a,b,x;
}e[500010];
struct forsort
{
int id,val;
}f[200010];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
bool cmpedge(edge a,edge b)
{
return a.x<b.x;
}
int read()
{
char c;
int s=0;
c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();
return s;
}
void init1()
{
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++)
{
f[i].id=i;
f[i].val=read();
}
sort(f+1,f+n+1,cmp);
tot=0;
for(int i=1;i<=n;i++)
{
if (i==1||f[i].val!=f[i-1].val)
pos[++tot]=f[i].val;
h[f[i].id]=tot;
top[i]=i;
topp[i]=i;
}
}
int find(int x)
{
int r=x,i=x,j;
while(r!=top[r]) r=top[r];
while(i!=r) j=top[i],top[i]=r,i=j;
return r;
}
void merge(int x,int y,int p)
{
int fx=find(x),fy=find(y);
top[fx]=fy;
topp[fy]=p;
}
void dfs(int v)
{
in[v]=tim;
siz[v]=0;
for(int i=0;i<=1;i++)
{
if (!ch[v][i]) continue;
dfs(ch[v][i]);
siz[v]+=siz[ch[v][i]];
}
if (!ch[v][0]&&!ch[v][1])
pp[++tim]=v,siz[v]=1;
out[v]=tim;
}
void buildtree(int &no,int l,int r)
{
no=++T;
if (l==r) return;
int mid=(l+r)>>1;
buildtree(segch[no][0],l,mid);
buildtree(segch[no][1],mid+1,r);
}
void insert(int &no,int last,int l,int r,int x)
{
no=++T;
seg[no]=seg[last];
segch[no][0]=segch[last][0];
segch[no][1]=segch[last][1];
if (l==r) {seg[no]++;return;}
int mid=(l+r)>>1;
if (x<=mid) insert(segch[no][0],segch[last][0],l,mid,x);
else insert(segch[no][1],segch[last][1],mid+1,r,x);
seg[no]=seg[segch[no][0]]+seg[segch[no][1]];
}
int query(int last,int no,int l,int r,int k)
{
if (l==r) return l;
int s=seg[segch[no][0]]-seg[segch[last][0]];
int mid=(l+r)>>1;
if (k<=s) return query(segch[last][0],segch[no][0],l,mid,k);
else return query(segch[last][1],segch[no][1],mid+1,r,k-s);
}
void init2()
{
for(int i=1;i<=m;i++)
e[i].a=read(),e[i].b=read(),e[i].x=read();
sort(e+1,e+m+1,cmpedge);
totp=n;
for(int i=1;i<=m;i++)
if (find(e[i].a)!=find(e[i].b))
{
int fx=find(e[i].a),fy=find(e[i].b);
++totp;
h[totp]=e[i].x;
fa[topp[fx]][0]=fa[topp[fy]][0]=totp;
ch[totp][0]=topp[fx],ch[totp][1]=topp[fy];
merge(e[i].a,e[i].b,totp);
}
tim=0;
for(int i=1;i<=n;i++)
if (!vis[topp[find(i)]])
{
vis[topp[find(i)]]=1;
dfs(topp[find(i)]);
}
buildtree(rt[0],1,tot);
for(int i=1;i<=n;i++)
insert(rt[i],rt[i-1],1,tot,h[pp[i]]);
for(int i=1;i<=18;i++)
for(int j=1;j<=totp;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
}
int findtop(int v,int x)
{
for(int i=18;i>=0;i--)
if (fa[v][i]&&h[fa[v][i]]<=x)
v=fa[v][i];
return v;
}
void work()
{
int lastans=0;
for(int i=1;i<=q;i++)
{
int v,x,k,t;
v=read(),x=read(),k=read();
//v^lastans,x^=lastans,k^=lastans;
t=findtop(v,x);
if (siz[t]<k)
{
printf("-1\n");
lastans=0;
continue;
}
lastans=pos[query(rt[in[t]],rt[out[t]],1,tot,siz[t]-k+1)];
printf("%d\n",lastans);
}
}
int main()
{
init1();
init2();
work();
return 0;
}