[bzoj3123][Sdoi2013]森林_主席树_启发式合并

森林 bzoj-3123 Sdoi-2013

题目大意:给定一片共n个点的森林,T个操作,支持:连接两个不在一棵树上的两个点;查询一棵树上路径k小值。

注释:$1\le n,T \le 8\cdot 10^4$

想法:运用冯老师讲的方法:

“对于一个开起来非常困难的问题,我们可以通过先构造拟对象,然后向完全对象转化”

这个题,我们看到了最后一个操作,想到了主席树。

两个点x、y之间的路径,我们在主席树上用root[x]+root[y]-root[lca(x,y)]-root[fa[lca(x,y)]]的权值线段树即可。

那么,如果加上连接操作呢?

直接启发式合并,然后重置一部分的主席树即可。(重置的时候记得把倍增的数组也重置)

最后,附上丑陋的代码... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
int cnt,tot,ans,len,a,b,tcase,n,m,q;
char s[5];
int x,y,z,d[80010],h[80010],v[80010];
map<int,int>g;
int to[200010],vis[80010],anc[80010],size[80010],ls[20000010],rs[20000010],nxt[200010],head[200010],f[80010][26],sum[20000010],root[2000010];
inline void add(int x,int y)
{
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
int lca(int x,int y)
{
    if(d[x]<d[y])
    {
        swap(x,y);
    }
    int dep=d[x]-d[y];
    for(int i=0;i<=23;i++)
    {
        if((dep&(1<<i))!=0)
        {
            x=f[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=23;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int updata(int pre,int l,int r,int v)
{
    int pos=++cnt,mid=(l+r)>>1;
    ls[pos]=ls[pre];
    rs[pos]=rs[pre];
    sum[pos]=sum[pre]+1;
    if(l==r)
    {
        return pos;
    }
    else
    {
        if(v<=mid)
        {
            ls[pos]=updata(ls[pre],l,mid,v);
        }
        else
        {
            rs[pos]=updata(rs[pre],mid+1,r,v);
        }
    }
    return pos;
}
int query(int x,int y,int fa,int anc,int l,int r,int k)
{
	int mid=(l+r)>>1;
    if(l==r)
    {
        return g[l];
    }
    int num=sum[ls[x]]+sum[ls[y]]-sum[ls[fa]]-sum[ls[anc]];
    if(k<=num)
    {
        return query(ls[x],ls[y],ls[fa],ls[anc],l,mid,k);
    }
    else
    {
        return query(rs[x],rs[y],rs[fa],rs[anc],mid+1,r,k-num);
    }
}
void dfs(int x,int fa,int ac)
{
    vis[x]=1;
    anc[x]=ac;
    f[x][0]=fa;
    d[x]=d[fa]+1;
    root[x]=updata(root[fa],1,n,v[x]);
    for(int i=1;i<=23;i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(int i=head[x];i;i=nxt[i])
    {
        if(to[i]!=fa)
        {
            dfs(to[i],x,ac);
            size[x]+=size[to[i]];
        }
    }
}
int main()
{
    scanf("%d",&tcase);
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        h[i]=v[i];
        size[i]=1;
    }
    sort(h+1,h+1+n);
    len=unique(h+1,h+1+n)-h-1;
    for(int i=1;i<=n;i++)
    {
        int val=v[i];
        v[i]=lower_bound(h+1,h+1+len,v[i])-h;
        g[v[i]]=val;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        dfs(i,0,i);
    }
    while(q--)
    {
        scanf("%s",s);
        if(s[0]=='Q')
        {
            scanf("%d%d%d",&x,&y,&z);
            x=x^ans;
            y=y^ans;
            z=z^ans;
            a=lca(x,y);
            b=f[a][0];
            ans=query(root[x],root[y],root[a],root[b],1,n,z);
            printf("%d\n",ans);
        }
        else
        {
            scanf("%d%d",&x,&y);
            x=x^ans;
            y=y^ans;
            add(x,y);
            add(y,x);
            if(size[anc[x]]<size[anc[y]])
            {
                size[anc[y]]+=size[anc[x]];
                dfs(x,y,anc[y]);
            }
            else
            {
                size[anc[x]]+=size[anc[y]];
                dfs(y,x,anc[x]);
            }
        }
    }
}

小结:主席树活用起来真tm吓人... ...

posted @ 2018-07-24 21:31  JZYshuraK_彧  阅读(236)  评论(0编辑  收藏  举报