[SDOI2013]森林(启发式合并)(主席树)

题目描述
小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有M条边。

小Z希望执行T个操作,操作有两类:

Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
L x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。

对于一个输入的操作Q x y k,其真实操作为Q x^lastans y^lastans k^lastans。
对于一个输入的操作L x y,其真实操作为L x^lastans y^lastans。其中^运算符表示异或,等价于pascal中的xor运算符。
请写一个程序來帮助小Z完成这些操作。

对于所有的数据,n,m,T<=8*10^48∗10
4

输入格式
第一行包含一个正整数testcase,表示当前测试数据的测试点编号。保证1<=testcase<=20。

第二行包含三个整数N,M,T,分别表示节点数、初始边数、操作数。

第三行包含N个非负整数表示 N个节点上的权值。

接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边。

接下来 T行,每行描述一个操作,格式为”Q x y k“或者”L x y “,其含义见题目描述部分。

题目要求的是实现两个操作:查询区间k小值和连边操作。

查询:主席树

连边:LCT

主席树+LCT

首先码量就有一定压力、程序的实现性,但是也不是不能做。

那有没有一些更容易实现的方法呢?

看起来好像区间k小值应该是主席树跑不掉了,那连边操作呢?

好像可以合并主席树。

但又好像直接时间不太行,那怎么办?

答:主席树+LCT

其实可以启发式合并,对每一棵原树记录它的size,每一次合并的时候就相当于将一棵树合并到另一棵树上,启发式合并,将size小的合并到size大的上面去,每次合并的时候用并查集记录,修改时直接找到并查集顶端的元素即可。

题目要求的是树上的区间k小,所以要对于树建主席树,其实就是儿子在爸爸的主席树的基础上添加,跟普通的主席树没有太大的区别,仅仅需要dfs建树罢了。

建树:

void insert(int &hao,int last,int l,int r,int x)//插入点
{
    hao=++cnt;
    tr[hao]=tr[last];
    tr[hao].size++;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)
    {
        insert(tr[hao].lson,tr[last].lson,l,mid,x);
    }else{
        insert(tr[hao].rson,tr[last].rson,mid+1,r,x);
    }
}
void dfs(int u,int root,int father)//建树(在爸爸的基础上)
{
    fa[u][0]=father;//这里更新lca,查询要用
    for(int i=1;i<=17;i++)
    {
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    vis[u]=1;
    size[root]++;
    deep[u]=deep[father]+1;
    fas[u]=father;
    insert(rt[u],rt[father],1,cnt1,lower_bound(b+1,b+cnt1+1,a[u])-b);
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v!=father)
        {
            dfs(v,root,u);
        }
    }
}

查询时就是像普通主席树一样。

[SDOI2013]森林(启发式合并)(主席树)

题目要求的是一条路径,所以需要四个地方的size:lca的父亲,lca,x,y

像图中这样x到根的路径,y到根的路径减去lca到根的路径,lca的父亲到根的路径,就得到了要求的路径。

这条路径上的点数就是\(x.size+y.size-lca.size-lca的父亲.size\),同理,左边的主席树的size也可以求的,从而确定是往左找还是往右找。

查询:

int query(int hao,int hao1,int lca,int falca,int l,int r,int k)
{
    if(l==r)
    {
        return b[l];
    }
    int mid=(l+r)/2
    int midsize=tr[tr[hao].lson].size+tr[tr[hao1].lson].size-tr[tr[lca].lson].size-tr[tr[falca].lson].size;//主席树左边的size
    if(midsize>=k)//往左找或往右找
    {
        return query(tr[hao].lson,tr[hao1].lson,tr[lca].lson,tr[falca].lson,l,mid,k);
    }else{
        return query(tr[hao].rson,tr[hao1].rson,tr[lca].rson,tr[falca].rson,mid+1,r,k-midsize);
    }
}

程序:

#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define N 80010
using namespace std;
struct data
{
    int size,lson,rson;
}tr[N*500];
int to[N<<2],nxt[N<<2],nct,head[N],fas[N],fa[N][18],cnt,rt[N],cnt1,a[N],b[N],size[N],deep[N],n,m,k,x,y,t,lastans;
char s[3];
bool vis[N];
void adde(int x,int y)
{
    to[++nct]=y;
    nxt[nct]=head[x];
    head[x]=nct;
}
int get_fa(int x)//并查集找爸爸
{
    if(fas[x]==x)
    {
        return x;
    }
    return fas[x]=get_fa(fas[x]);
}
void build(int &hao,int l,int r)//建一棵空的树,便于操作
{
    hao=++cnt;
    tr[hao].size=0;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)>>1;
    build(tr[hao].lson,l,mid);
    build(tr[hao].rson,mid+1,r);
}
void insert(int &hao,int last,int l,int r,int x)//插入点
{
    hao=++cnt;
    tr[hao]=tr[last];
    tr[hao].size++;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)
    {
        insert(tr[hao].lson,tr[last].lson,l,mid,x);
    }else{
        insert(tr[hao].rson,tr[last].rson,mid+1,r,x);
    }
}
void dfs(int u,int root,int father)//建树(在爸爸的基础上)
{
    fa[u][0]=father;//这里更新lca,查询要用
    for(int i=1;i<=17;i++)
    {
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    vis[u]=1;
    size[root]++;
    deep[u]=deep[father]+1;
    fas[u]=father;
    insert(rt[u],rt[father],1,cnt1,lower_bound(b+1,b+cnt1+1,a[u])-b);
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v!=father)
        {
            dfs(v,root,u);
        }
    }
}
int get_lca(int x,int y)
{
    if(deep[x]<deep[y])
    {
        swap(x,y);
    }
    for(int i=17;i>=0;i--)
    {
        if(deep[fa[x][i]]>=deep[y])
        {
            x=fa[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=17;i>=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int query(int hao,int hao1,int lca,int falca,int l,int r,int k)
{
    if(l==r)
    {
        return b[l];
    }
    int mid=(l+r)/2,midsize=tr[tr[hao].lson].size+tr[tr[hao1].lson].size-tr[tr[lca].lson].size-tr[tr[falca].lson].size;//主席树左边的size
    if(midsize>=k)//往左找或往右找
    {
        return query(tr[hao].lson,tr[hao1].lson,tr[lca].lson,tr[falca].lson,l,mid,k);
    }else{
        return query(tr[hao].rson,tr[hao1].rson,tr[lca].rson,tr[falca].rson,mid+1,r,k-midsize);
    }
}
int main()
{
//  freopen("1.txt","r",stdin);
    scanf("%d",&n);
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
        fas[i]=i;
    }
    sort(b+1,b+n+1);//离散化
    cnt1=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        adde(x,y);
        adde(y,x);
    }
    build(rt[0],1,cnt1);
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])
        {
            dfs(i,i,0);
            fas[i]=i;//重新确定并查集顶端元素,不然会TLE
        }
    }
    while(t--)//操作
    {
        scanf("%s%d%d",s,&x,&y);
        x^=lastans;
        y^=lastans;
        if(s[0]=='Q')
        {
            scanf("%d",&k);
            k^=lastans;
            int lca=get_lca(x,y);
            printf("%d\n",lastans=query(rt[x],rt[y],rt[lca],rt[fa[lca][0]],1,cnt1,k));
        }else{
            adde(x,y);
            adde(y,x);
            int xx=get_fa(x),yy=get_fa(y);
            if(size[xx]<size[yy])
            {
                swap(xx,yy);
                swap(x,y);
            }
            dfs(y,xx,x);
        }
    }
    return 0;
}
上一篇:Python定制化二维码


下一篇:【BZOJ3489】A simple rmq problem(K-D Tree)