P3302 森林(启发式合并+主席树)

刚刚通过代码我就来写题解了,我是不是很有责任感(其实是被卡的脑瘫了),那么我就来写一下这个简(du)单(liu)的题。

思路

查询路径上的第k大的值,可以用主席树优化,例如对(x,y)路径上(必须联通)查询第k大,可以用一个主席树保存根节点到当前节点x路径上所有节点权值,然后在查询(x,y)时可以用{x,y}的查询区间相加,减去{ lca(x,y),lca(x,y)的父节点 }相加,这样得到的主席树是(x,y)上所有权值的主席树,然后根据判断第k大的方式向下查询

int query(int x,int y,int pre1,int pre2,int l,int r,int k)//x,y是查询路径的两端,pre1是lca(x,y),pre2是lca(x,y)的父节点
{
    
    if(l==r)return b[l];
    int lastsize=t[t[x].lc].dat+t[t[y].lc].dat-t[t[pre1].lc].dat-t[t[pre2].lc].dat;
    int mid=l+r>>1;
    if(k<=lastsize)return query(t[x].lc,t[y].lc,t[pre1].lc,t[pre2].lc,l,mid,k);
    return query(t[x].rc,t[y].rc,t[pre1].rc,t[pre2].rc,mid+1,r,k-lastsize);
}

真正让人难以琢磨的是连边,需要我们启发式合并主席树。事实上,就是在连边后,将轻树以连边的一端为起点,连边另一端为起点的父节点,跑一遍dfs。任意一颗线段树,都是根据父节点建立的(每一棵树的根节点的父节点都认为是0),所以只需要再跑dfs时将当前遍历的节点在父节点的基础上建立就好。

void dfs(int now,int father,int rt)
{
    
    Insert(root[now],root[father],1,SIZE,c[a[now]]);//正常的值传递建树就好
    for(int i=head[now];i;i=Next[i])
    {
        int to=ver[i];
        if(to==father)continue;
        dfs(to,now,rt);
    }
}

好了好了,题目讲完了,只要注意到数据的第一个数字时测试数据的编号,就不会像我一样只过了第一个点。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define N 80005 
#define lson now<<1
#define ll long long
#define mod 1000000007
#define rep(i,s,t) for(int i=s;i<=t;i++)

//并查集求祖先,st表和dep数组查lca,num通过祖先记录树的轻重
int fa[N]={},st[N][18],num[N],dep[N];

//对a离散化,将建树的值区间压缩为80000,c储存a[i]按照大小排序的序号
int a[N],b[N],SIZE;
map<int,int>c;

//邻接表存图
int tot=0;
int ver[N*4],Next[N*4],head[N*4];
bool vis[N];

//主席树,因为每一次连边都要重新建树,所以要开的很大
int root[N];
struct SegTree{
    int dat;
    int lc,rc;
}t[N*600];
int cnt=0;

inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
void add(int x,int y)
{
    ver[++tot]=y;
    Next[tot]=head[x],head[x]=tot;
} 
void build(int &now,int l,int r)
{
    now=++cnt;
    t[now].dat=0;
    if(l==r)return ; 
    int mid=l+r>>1;
    build(t[now].lc,l,mid);
    build(t[now].rc,mid+1,r); 
}
void Insert(int &now,int father,int l,int r,int x)
{
    now=++cnt;
    t[now]=t[father];
    t[now].dat++;;
    if(l==r)return ;
    int mid=l+r>>1;
    if(x<=mid)Insert(t[now].lc,t[father].lc,l,mid,x);
    if(x>mid)Insert(t[now].rc,t[father].rc,mid+1,r,x);
}
int query(int x,int y,int pre1,int pre2,int l,int r,int k)
{
    
    if(l==r)return b[l];
    int lastsize=t[t[x].lc].dat+t[t[y].lc].dat-t[t[pre1].lc].dat-t[t[pre2].lc].dat;
    int mid=l+r>>1;
    if(k<=lastsize)return query(t[x].lc,t[y].lc,t[pre1].lc,t[pre2].lc,l,mid,k);
    return query(t[x].rc,t[y].rc,t[pre1].rc,t[pre2].rc,mid+1,r,k-lastsize);
}
void dfs(int now,int father,int rt)
{
    st[now][0]=father;
    rep(i,1,16)st[now][i]=st[st[now][i-1]][i-1];
    fa[now]=father;
    num[rt]++;
    dep[now]=dep[father]+1;
    vis[now]=1;
    Insert(root[now],root[father],1,SIZE,c[a[now]]);
    for(int i=head[now];i;i=Next[i])
    {
        int to=ver[i];
        if(to==father)continue;
        dfs(to,now,rt);
    }
}
int LCA(int x,int y)
{
    if(dep[x]>dep[y])//强制要求x节点是在下方的节点
        swap(x,y);//交换,维持性质
    for(int k=16;k>=0;k--){
        if(dep[st[y][k]]>=dep[x]){
            y=st[y][k];
        }}
    if(x==y)//发现Lca(x,y)=y
        return x;//返回吧,找到了..
    for(int k=16;k>=0;k--){
        if(st[x][k]!=st[y][k]){
            x=st[x][k];
            y=st[y][k];
        }
    }
    return st[x][0];//必须返回x的父亲节点,也就是Lca(x,y)
}


int _find(int x){return fa[x]==x?x:_find(fa[x]);}
void solve()
{
    int n,m,T,x,y;
    cnt=tot=0,c.clear();
    memset(head,0,sizeof(head));
    memset(head,0,sizeof(st));
    n=rd(),m=rd(),T=rd();
    rep(i,1,n)
    {
        a[i]=rd();
        b[i]=a[i];
        fa[i]=i;
        dep[i]=num[i]=0;
        vis[i]=0;
    }
    sort(b+1,b+n+1);
    SIZE=unique(b+1,b+1+n)-b-1;
    rep(i,1,SIZE)c[b[i]]=i;
    dep[0]=0;
    build(root[0],1,SIZE);
    rep(i,1,m)
    {
        x=rd(),y=rd();
        add(x,y),add(y,x);
    }
    for(int i=1;i<=n;i++)if(!vis[i])
    {
        dfs(i,0,i);
        fa[i]=i;
    }
    int lastans=0;
    rep(i,1,T)
    {
        char ch[3];
        int x,y,k;
        scanf("%s",&ch);
        if(ch[0]=='Q')
        {
            x=rd(),y=rd(),k=rd();
            x^=lastans;
            y^=lastans;    
            k^=lastans;        
            int z=LCA(x,y);
            lastans=query(root[x],root[y],root[z],root[st[z][0]],1,SIZE,k);
            printf("%d\n",lastans);
        }
        if(ch[0]=='L')
        {
            x=rd(),y=rd();
            x^=lastans;
            y^=lastans;    
            int u=_find(x);
            int v=_find(y);
            add(x,y),add(y,x);
            
            if(num[u]>=num[v])
            {
                
                dfs(y,x,u);
            }
            else
            {
                dfs(x,y,v);
            }    
        }    
    }
}
int main()
{
    int T;
    scanf("%d",&T);T=1;
    while(T--)
    {
        solve();
     } 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值