【BZOJ3123】【SDOI2013】【XSY1660】森林(启发式合并,主席树)

综合性比较强的一道题。

据说有 L C T LCT LCT+主席树的毒瘤做法,我也不知道这群人码这个东西的信心从哪来 但人家到底是码出来了

对于询问,我们考虑用主席树实现。

关键是怎么建才能维护一条路径上的。

对于原树中的节点 u u u,我们在权值线段树中维护的是从根到 u u u的所有点的点权。

那么对于 u u u v v v的路径上的点,我们先计算出 l c a ( u , v ) lca(u,v) lca(u,v)以及 f a [ l c a ] fa[lca] 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);
}

m i n u s minus minus是什么意思呢,我们可以画个图看一下:

在这里插入图片描述

其中红色的为我们加上的部分,蓝色的为我们减去的部分,剩下的就是 u → v u\rightarrow v uv的路径了。

所以我们就可以用这种方法算第 k k k大了。

对于修改,我们考虑用启发式合并

我们每次加边把 s i z e size size小的树往 s i z e size size大的树合并。

然后暴力遍历那棵 s i z e 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);//暴力遍历
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值