NAIVE-04

[文章目录]

STL-set&multiset

以前不太会用,现在觉得挺好。(不过我不知道能不能用这个查元素排名,还有讲真常数挺大的 eg. bzoj3685会TLE)

二者都是用来维护数据结构的,插入的元素会排好序,每一次操作的复杂度都是log的

基本的函数啥的就不提了,度娘啥都有。

注意的几点:

1.set:元素不可重。multiset:元素可重

2.有一个神一样的函数s.count(x)返回一个int,表示集合中有多少个x,如果是set的话,只会返回1||0,表示有无元素,然而这tm是O(n)的。并不建议运用

3.lower_bound(x)返回iterator。指向优先级>=x的最前面的元素,如果没有,返回s.end( )

4.upper_bound(x)返回iterator。指向优先级>x的最前面的元素,如果没有,返回s.end( )

[写代码的时候一定要注意,呜呜呜,水题bz1208狂WA]

5.s.begin()指向s中第一个元素,s.end()指向最后面那个元素的后面,也就是s集合为[begin,end),两个都一样。

6.迭代器只提供++或是--,但不能超范围。

7.s.erase(s,t),删除集合中[s,t)中的元素。其中,s,t为iterator。如果想删元素需要把上面的lower_bound()和upper_bound()连用,注意删除区间是指针的左闭右开。两个都适用

8.s.clear()清空集合

9.s.erase(x),可以是删除x这个元素,如果x是指针的话就是删除这个指针指向的元素。注意,如果s是multiset的话,会把所有的x元素都删除。

10.s.find(x),返回一个指向元素x的迭代器,如果没有,返回end()。

另外发现了一个很好的blog,描述STL的函数很全面:【戳这里】

非旋转treap 好记好用

我曾经跨过山和大海,也穿过人山人海。也没有发现什么数据结构听一遍就直接会用。splay,treap,树状数组。。。理解+运用都需要好长一段时间。直到CKH发现了一个新的领域:非旋转treap!!!

同样为了使复杂度正确,非旋转treap给每个节点附上一个新的权值key。然后在合并的时候,维护一个堆(下文为大根堆)。同样也是中序遍历。

只有两个神操作;

1.merge(x,y) 将x,y为根的树合并

由于要维护堆,所以比较key[x],key[y]。将较大的当作新的根。然后合并大根的一个子树与另一棵树,递归。注意,x+y的中序遍历不变,也就是x,y合并的正确性建立在x+y的中序遍历的正确性上。

2.split(x,y)将以x为根的树在中序遍历的前y个与后面分开

返回一个pair,为新的两颗树的根。然后判断各种情况递归进行。

各种操作均可以用这两个操作实现,什么打lazy,插入,删除......
不过每次操作后的树都应为一颗以root为根的树,注意,是一颗。

下面附上普通平衡树的非旋转treap代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
#define mp(x,y) make_pair(x,y)
using namespace std;
typedef pair<int,int> pr;
int root,n,tot,op;
int ls[N],rs[N],w[N],siz[N],key[N];
void updata(int pos)
{
    siz[pos]=siz[ls[pos]]+siz[rs[pos]]+1;
}
pr split(int x,int y)
{
    if(y==0) return mp(0,x);
    if(y==siz[ls[x]])
    {
        int t=ls[x]; ls[x]=0;
        updata(x);
        return mp(t,x);
    }
    if(y==siz[ls[x]]+1)
    {
        int t=rs[x];
        rs[x]=0; updata(x);
        return mp(x,t);
    }
    if(y<siz[ls[x]])
    {
        pr t=split(ls[x],y);
        ls[x]=t.second; updata(x);
        return mp(t.first,x);
    }
    pr t=split(rs[x],y-siz[ls[x]]-1);
    rs[x]=t.first; updata(x);
    return mp(x,t.second);
}
int merge(int x,int y)
{
    if(!x||!y) return x|y;
    if(key[x]<key[y])
    {
        rs[x]=merge(rs[x],y);
        updata(x);
        return x;
    }
    ls[y]=merge(x,ls[y]);
    updata(y);
    return y;
}
void insert(int x,int y)
{
    if(!root)
    {
        w[++tot]=y; siz[tot]=1; key[tot]=rand()*rand();
        root=tot;
        return ;
    }
    int l=0;
    while(x)
    {
        if(w[x]>=y) x=ls[x];
        else l+=siz[ls[x]]+1,x=rs[x];
    }
    pr t=split(root,l);
    w[++tot]=y; siz[tot]=1; key[tot]=rand()*rand();
    t.first=merge(t.first,tot);
    root=merge(t.first,t.second);
}
void del(int x,int y)
{
    int l=0;
    while(x)
    {
        if(w[x]>=y) x=ls[x];
        else l+=siz[ls[x]]+1,x=rs[x];
    }
    pr t=split(root,l);
    t.second=split(t.second,1).second;
    root=merge(t.first,t.second);
}
void q_rank(int x,int y)
{
    int ans=0;
    while(x)
    {
        if(w[x]>=y) x=ls[x];
        else ans+=siz[ls[x]]+1,x=rs[x];
    }
    printf("%d\n",ans+1);
}
void q_val(int x,int y)
{
    while(x)
    {
        if(y<=siz[ls[x]]) x=ls[x];
        else
        {
            y-=siz[ls[x]]+1;
            if(!y){printf("%d\n",w[x]); return ;}
            x=rs[x];
        }
    }
}
void q_before(int x,int y)
{
    int l=0;
    while(x)
    {
        if(w[x]>=y) x=ls[x];
        else l=x,x=rs[x];
    }
    printf("%d\n",w[l]);
}
void q_after(int x,int y)
{
    int r;
    while(x)
    {
        if(w[x]<=y) x=rs[x];
        else r=x,x=ls[x];
    }
    printf("%d\n",w[r]);
}
int main()
{
    scanf("%d",&n);
    int x;
    while(n--)
    {
        scanf("%d%d",&op,&x);
        switch(op)
        {
            case 1:insert(root,x); break;
            case 2:del(root,x); break;
            case 3:q_rank(root,x); break;
            case 4:q_val(root,x); break;
            case 5:q_before(root,x); break;
            case 6:q_after(root,x); break;
        }
    }
    return 0;
}

RMQ&&LCA

额,前置技能perfect!!!。
先认识一个叫做欧拉遍历序的东西,就是在dfs的过程中当第一次搜到一个点的时候记录这个点,当一个儿子搜完之后再记录这个点的序列。大概是这个意思:

void dfs(int x)
{
    in[x]=++tot; rank[tot]=x; deep[x]=deep[fa[x]]+1;
    for(int i=head[x];i;i=nxt[i])
        if(to[i]!=fa[x])
        {
            fa[to[i]]=x;
            dfs(to[i]);
            rank[++tot]=x;
        }
}

我们发现这个序列每条边退出的时候起始点被记录了一次,每个点在dfs到的时候被记录了一次,所以共有2n-1个点。
发现这样一个性质,任意两个点入栈时间戳对应的闭区间中deep最小的点就是两点的lca。
那么我们只需要知道区间deep最小值所在的点就好了。我们可以进行RMQ操作,每次比较区间的ans哪一个deep最小,然后在查询的时候就可以O(1)出解啦。2333
具体写法:
1.求出欧拉遍历序:之前写了
2.预处理:

for(int i=2;i<=tot;i++) Log[i]=Log[i>>1]+1;
for(int i=1;i<=tot;i++) lca[i][0]=rank[i];
for(int i=1;i<=20;i++)
    for(int j=1;j+(1<<i)-1<=tot;j++)
        lca[j][i]=deep[lca[j][i-1]] < deep[lca[min(j+(1<<i-1),tot)][i-1]]?lca[j][i-1]:lca[min(j+(1<<i-1),tot)][i-1];

3.O(1)查询:

if(in[x]>in[y]) swap(x,y);
tmp=Log[in[y]-in[x]+1];
anc=deep[lca[in[x]][tmp]]<deep[lca[in[y]-(1<<tmp)+1][tmp]]?lca[in[x]][tmp]:lca[in[y]-(1<<tmp)+1][tmp];

新技能get√。

发表评论

邮箱地址不会被公开。 必填项已用*标注