[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);
        }
    }
}

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

题目要求的是一条路径,所以需要四个地方的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;
}

原文地址:https://www.cnblogs.com/2017gdgzoi44/p/12111006.html

时间: 2024-11-15 08:13:43

[SDOI2013]森林(启发式合并)(主席树)的相关文章

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

对着题目yy了一天加上看了一中午题解,终于搞明白了我太弱了 连边就是合并线段树,把小的集合合并到大的上,可以保证规模至少增加一半,复杂度可以是\(O(logn)\) 合并的时候暴力dfs修改倍增数组和维护主席树即可 然后树上主席树就是维护节点到根节点的信息即可, 询问链上的第k大时,画图后可以发现维护一个rootx,rooty,rootlca和rootfalca即可解决问题 注意空间要开够 然后注意细节,没了 #include <cstdio> #include <cstring>

[SDOI2013]森林(树上主席树)

[SDOI2013]森林(luogu) Description 题目描述 小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值.初始的时候,森林中有M条边. 小Z希望执行T个操作,操作有两类: Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少.此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点. L x y在点x和点y之间连接一条边.保证完成此操作后,仍然是一片森林. 为了体现程序的在线性,我们把输入数据进行了加密.设lastans为程序上一次输出的结果,

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样

bzoj2733: [HNOI2012]永无乡(splay+启发式合并/线段树合并)

这题之前写过线段树合并,今天复习Splay的时候想起这题,打算写一次Splay+启发式合并. 好爽!!! 写了长长的代码(其实也不长),只凭着下午的一点记忆(没背板子...),调了好久好久,过了样例,submit,1A! 哇真的舒服 调试输出懒得删了QwQ #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> #include

启发式合并&amp;线段树合并&amp;treap合并&amp;splay合并

启发式合并 有\(n\)个集合,每次让你合并两个集合,或询问一个集合中是否存在某个元素. ? 我们可以用平衡树/set维护集合. ? 对于合并两个\(A,B\),如果\(|A|<|B|\),那么我们就把\(A\)中的每个元素暴力加到\(B\)中,否则就把\(B\)中的元素暴力加到\(A\)中. ? 对于一次把\(A\)中的每个元素暴力加到\(B\)中的操作,\(|A|\)会变成\(|A|+|B|\),也就是说大小至少会翻倍,所以一个元素最多被暴力插入\(\log n\)次.每次插入的时间复杂度是

HDU - 4358 Boring counting (树上启发式合并/线段树合并)

题目链接 题意:统计树上每个结点中恰好出现了k次的颜色数. dsu on tree/线段树合并裸题. 启发式合并1:(748ms) 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10; 5 int n,m,k,a[N],b[N],nb,fa[N],son[N],siz[N],cnt[N],ans[N],now,ne,hd[N],ka; 6 struct E {

CodeForces - 778C: Peterson Polyglot (启发式合并trie树)

Peterson loves to learn new languages, but his favorite hobby is making new ones. Language is a set of words, and word is a sequence of lowercase Latin letters. Peterson makes new language every morning. It is difficult task to store the whole langua

[POI2011]ROT-Tree Rotations 线段树合并|主席树 / 逆序对

题目[POI2011]ROT-Tree Rotations [Description] 现在有一棵二叉树,所有非叶子节点都有两个孩子.在每个叶子节点上有一个权值(有\(n\)个叶子节点,满足这些权值为\(1..n\)的一个排列).可以任意交换每个非叶子节点的左右孩子. 要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少. [Input Format] 第一行一个整数\(n\). 下面每行,一个数\(x\). 如果\(x =0\),表示这个节点非叶子节点,递归地向下读

Luogu_P3302 [SDOI2013]森林【题解】主席树 lca 启发式合并

# Luogu_P3302 [SDOI2013]森林 主席树,启发式合并,lca luogu题面 求树上路径的第k大,树之间还有合并. 明显是主席树再加合并. 先说链上第k大,其实就是$Tx+Ty-Tlca-Tlcafa$ $T$表示权值线段树. 主席树维护的是从根节点到当前节点的前缀和. ask的代码如下: inline int ask(int x,int y,int lcc,int lcf,int l,int r,int k){ if(l==r) return b[l]; int lz=su