【BZOJ3123】【SDOI2013】【XSY1660】森林(启发式合并,主席树)
综合性比较强的一道题。
据说有\(LCT\)+主席树的毒瘤做法,我也不知道这群人码这个东西的信心从哪来 但人家到底是码出来了
对于询问,我们考虑用主席树实现。
关键是怎么建才能维护一条路径上的。
对于原树中的节点\(u\),我们在权值线段树中维护的是从根到\(u\)的所有点的点权。
那么对于\(u\)到\(v\)的路径上的点,我们先计算出\(lca(u,v)\)以及\(fa[lca]\),然后我们在询问时这么计算:
int query(int u,int v,int lca,int falca,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1,minus=
t[t[u].ch[0]].size+t[t[v].ch[0]].size-t[t[lca].ch[0]].size-t[t[falca].ch[0]].size;
if(minus>=k)
return query(t[u].ch[0],t[v].ch[0],t[lca].ch[0],t[falca].ch[0],l,mid,k);
else
return query(t[u].ch[1],t[v].ch[1],t[lca].ch[1],t[falca].ch[1],mid+1,r,k-minus);
}
\(minus\)是什么意思呢,我们可以画个图看一下:
其中红色的为我们加上的部分,蓝色的为我们减去的部分,剩下的就是\(u\rightarrow v\)的路径了。
所以我们就可以用这种方法算第\(k\)大了。
对于修改,我们考虑用启发式合并。
我们每次加边把\(size\)小的树往\(size\)大的树合并。
然后暴力遍历那棵\(size\)小的树,并更新树中点的倍增数组和主席树。
完整代码如下:
#include<bits/stdc++.h>
#define LN 17
#define N 80010
#define lc t[u].ch[0]
#define rc t[u].ch[1]
using namespace std;
struct Tree
{
int ch[2],size;
}t[N<<7];
int testcase,n,m,q;
int tot,root[N*LN];
int nn,val[N],b[N];
int cnt,head[N],nxt[N<<1],to[N<<1];
int fa[N][LN],d[N],f[N],size[N];
bool vis[N];
void adde(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
void work()
{
sort(b+1,b+n+1);
nn=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
val[i]=lower_bound(b+1,b+nn+1,val[i])-b;
}
int build(int l,int r)
{
int u=++tot;
if(l==r) return 0;
int mid=(l+r)>>1;
lc=build(l,mid);
rc=build(mid+1,r);
return u;
}
void insert(int &u,int last,int l,int r,int val)
{
u=++tot;
t[u]=t[last],t[u].size++;
if(l==r) return;
int mid=(l+r)>>1;
if(val<=mid) insert(lc,t[last].ch[0],l,mid,val);
else insert(rc,t[last].ch[1],mid+1,r,val);
}
int query(int u,int v,int lca,int falca,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1,minus=t[t[u].ch[0]].size+t[t[v].ch[0]].size-t[t[lca].ch[0]].size-t[t[falca].ch[0]].size;//上面讲到的计算方法
if(minus>=k) return query(t[u].ch[0],t[v].ch[0],t[lca].ch[0],t[falca].ch[0],l,mid,k);
else return query(t[u].ch[1],t[v].ch[1],t[lca].ch[1],t[falca].ch[1],mid+1,r,k-minus);
}
void dfs(int u,int rt)
{
vis[u]=true,size[rt]++;
for(int i=1;i<=16;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];//更改倍增数组
insert(root[u],root[fa[u][0]],1,nn,val[u]);//更新主席树
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==f[u])continue;
fa[v][0]=u,f[v]=u;
d[v]=d[u]+1;
dfs(v,rt);
}
}
int LCA(int a,int b)
{
if(d[a]<d[b]) swap(a,b);
for(int i=16;i>=0;i--)
if(fa[a][i]&&d[fa[a][i]]>=d[b])
a=fa[a][i];
if(a==b) return a;
for(int i=16;i>=0;i--)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
int main()
{
scanf("%d%d%d%d",&testcase,&n,&m,&q);
for(int i=1;i<=n;i++)
scanf("%d",&val[i]),b[i]=val[i];
work();
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
adde(u,v),adde(v,u);
}
root[0]=build(1,nn);
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
dfs(i,i);
f[i]=i;
}
}
int ans=0;
while(q--)
{
char ch=getchar();
int u,v;
while(ch!='Q'&&ch!='L') ch=getchar();
scanf("%d%d",&u,&v);
u^=ans,v^=ans;
if(ch=='Q')
{
int k;
scanf("%d",&k);
k^=ans;
int lca=LCA(u,v);
printf("%d\n",ans=b[query(root[u],root[v],root[lca],root[fa[lca][0]],1,nn,k)]);
}
if(ch=='L')
{
adde(u,v),adde(v,u);
int a=find(u),b=find(v);
if(size[a]<size[b])
{
swap(a,b);
swap(u,v);
}
fa[v][0]=u,f[v]=u;
d[v]=d[u]+1;
dfs(v,a);//暴力遍历
}
}
}