[BZOJ 3123] 森林

Link:

BZOJ 3123 传送门

Solution:

主席树+启发式合并

 

以前好像做的主席树都是在序列上的……在树上的主席树这样处理:

每个节点的主席树维护其到根节点的路径上的值,以其父节点为模板构造

那么为了取出$(u,v)$路径上的值用$seg[x]+seg[y]-seg[lca]-seg[f[lca]]$就行了

这其实可以说是树上差分的基本套路,而主席树不就是利用了差分性质嘛

 

接下来就要处理合并了,有一种感性方式就是将小的连通块并入大的中

这样的方式就叫做启发式合并了($DSU on Tree$),可以证明其总复杂度为$O(n*log(n))$

证明:

对于每棵以$v$为根的子树而言,仅当$edge(v,f[v])$为轻边时才会将所有子树内节点更新

如果按每个点计算贡献的方式,根据树链剖分中的性质:一个点到根的路径上轻边/重链个数不超过$log(n)$

可以发现每个节点最多更新$log(n)$次,因此复杂度上界就是$n*log(n)$

 

实现时用并查集维护连通性,每次找到$x,y$的代表节点$f[x],f[y]$,假设$sz[x]<sz[y]$

合并时图中将$x,y$连边,并查集中将$f[x],f[y]$连边

并将以$x$为根的子树中的点以$y$节点为初始点进行暴力更新

根据上述证明每个点保证最多更新$log(n)$次,但更新主席树也要$log(n)$,因此总复杂度为$O(n*log(n)^2)$

 

Tip:这次空间又开小了……难道不是$O(n*log(n))$?以后是真要学学怎么算空间复杂度了

Code:

#include <bits/stdc++.h>

using namespace std;
const int MAXN=8e4+10,MAXM=20000005;
struct PrTree{int ls,rs,cnt;}seg[MAXM];//一开始空间又开小了…… 
struct edge{int nxt,to;}e[MAXN<<2];
int f[MAXN][25],dep[MAXN],sz[MAXN],fa[MAXN];
int T,n,m,t,x,y,k,rt[MAXN],head[MAXN],dat[MAXN],dsp[MAXN],etot,tot,cnt,res;

void add_edge(int from,int to)
{e[++etot].nxt=head[from];e[etot].to=to;head[from]=etot;}

int LCA(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    int t=dep[x]-dep[y];
    for(int i=0;i<=20;i++)
        if(t&(1<<i)) x=f[x][i];
    for(int i=20;~i;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return (x==y)?x:f[x][0];
}

void Insert(int pre,int &cur,int pos,int l,int r)
{
    cur=++cnt;seg[cur]=seg[pre];seg[cur].cnt++;
    if(l==r) return;int mid=(l+r)>>1;
    if(pos<=mid) Insert(seg[pre].ls,seg[cur].ls,pos,l,mid);
    else Insert(seg[pre].rs,seg[cur].rs,pos,mid+1,r);
}

int Query(int a,int b,int c,int d,int k,int l,int r)
{
    if(l==r) return dsp[l];
    int mid=(l+r)>>1,sum=seg[seg[a].ls].cnt+seg[seg[b].ls].cnt-seg[seg[c].ls].cnt-seg[seg[d].ls].cnt;
    if(k<=sum) return Query(seg[a].ls,seg[b].ls,seg[c].ls,seg[d].ls,k,l,mid);
    else return Query(seg[a].rs,seg[b].rs,seg[c].rs,seg[d].rs,k-sum,mid+1,r);
}

int Find(int x){return (fa[x]==x)?x:fa[x]=Find(fa[x]);}
void dfs(int x,int anc)
{
    dep[x]=dep[anc]+1;f[x][0]=anc;
    for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];
    Insert(rt[anc],rt[x],dat[x],1,tot);
    for(int i=head[x];i;i=e[i].nxt)
        if(e[i].to!=anc) dfs(e[i].to,x);
}
void Link(int x,int y)
{
    int posx=Find(x),posy=Find(y);
    if(sz[posx]>sz[posy]) swap(x,y),swap(posx,posy);
    sz[posy]+=sz[posx];fa[posx]=posy;
    add_edge(x,y);add_edge(y,x);dfs(x,y);
}

int main()
{
    scanf("%d%d%d%d",&T,&n,&m,&t);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]),dsp[i]=dat[i];
    sort(dsp+1,dsp+n+1);tot=unique(dsp+1,dsp+n+1)-dsp-1;
    for(int i=1;i<=n;i++) dat[i]=lower_bound(dsp+1,dsp+n+1,dat[i])-dsp;
    
    for(int i=1;i<=n;i++) sz[i]=1,fa[i]=i;
    for(int i=1;i<=m;i++)
        scanf("%d%d",&x,&y),Link(x,y);
    for(int i=1;i<=n;i++)//一定要对其它孤立点初始化 
        if(!dep[i]) dfs(i,0);
    
    for(int i=1;i<=t;i++)
    {
        char s[20];scanf("%s%d%d",s,&x,&y);
        x^=res;y^=res;
        if(s[0]=='Q')
        {
            scanf("%d",&k);k^=res;
            int lca=LCA(x,y),flca=f[lca][0];
            printf("%d\n",res=Query(rt[x],rt[y],rt[lca],rt[flca],k,1,tot));
        }
        else Link(x,y);
    }
    return 0;
}

 

posted @ 2018-07-23 22:36  NewErA  阅读(167)  评论(0编辑  收藏  举报