给新高一小朋友讲可合并数据结构

· · 算法·理论

list

链表,任何合并都是 \mathcal O(1) 的。

线段树

例一

注意到模板线段树合并需要用到 LCA,因此我们以 P3224 为例。

参考实现

#include<stdio.h>
#define N 100001
inline char nc()
{
    static char buf[99999],*l,*r;
    return l==r&&(r=(l=buf)+fread(buf,1,99999,stdin),l==r)?EOF:*l++;
}
inline void read(int&x)
{
    char c=nc();for(;c<'0'||'9'<c;c=nc());
    for(x=0;'0'<=c&&c<='9';x=(x<<3)+(x<<1)+(c^48),c=nc());
}
struct node
{
    int cnt;node*l,*r;
    inline node(){cnt=1;l=r=0;}
}*tre[N];
int n,m,a[N],c[N],f[N];
inline int find(const int&x){if(x^f[x])return f[x]=find(f[x]);return x;}
inline node*b(const int&l,const int&r,const int&p)
{
    node*i=new node;
    if(l^r)
    {
        int mid=l+r>>1;
        if(p<=mid)i->l=b(l,mid,p);
        else i->r=b(mid+1,r,p);
    }
    return i;
}
inline void onion(node*&i,node*&j)
{
    if(!i){i=j;return;}
    if(!j)return;
    i->cnt+=j->cnt;
    onion(i->l,j->l);onion(i->r,j->r);
}
inline int q(const node*i,const int&l,const int&r,const int&p)
{
    if(l==r)return l;
    int mid=l+r>>1;
    if(i->l&&p<=i->l->cnt)return q(i->l,l,mid,p);
    return q(i->r,mid+1,r,p-(i->l?i->l->cnt:0));
}
main()
{
    read(n);read(m);
    for(int i=0;i<n;read(a[i]),c[a[i]]=i+1,tre[i]=b(1,n,a[i]),f[i]=i,++i);
    for(int o,x,y;m--;)
    {
        read(x);read(y);x=find(--x);y=find(--y);
        if(x^y)onion(tre[x],tre[y]),f[y]=x;
    }
    read(m);
    for(int o,x,y;m--;)
    {
        for(;o=nc(),(o^'Q')&&(o^'B'););
        if(o^'Q')
        {
            read(x);read(y);x=find(--x);y=find(--y);
            if(x^y)onion(tre[x],tre[y]),f[y]=x;
        }
        else
        {
            read(x);read(y);x=find(--x);
            if(tre[x]->cnt<y)printf("-1\n");
            else printf("%d\n",c[q(tre[x],1,n,y)]);
        }
    }
}

例二

大家都会 LCA,讲 P4556。

参考实现

#include<stdio.h>
#include<algorithm>
#define N 100001
#define LG 17
using namespace std;
inline char nc()
{
    static char buf[99999],*l,*r;
    return l==r&&(r=(l=buf)+fread(buf,1,99999,stdin),l==r)?EOF:*l++;
}
inline void read(int&x)
{
    char c=nc();for(;c<'0'||'9'<c;c=nc());
    for(x=0;'0'<=c&&c<='9';x=(x<<3)+(x<<1)+(c^48),c=nc());
}
struct node{int maxi,maxn,l,r;}a[N<<5];
int n,m,p,h[N],e[N<<1],nxt[N<<1],dep[N],jmp[N][LG],ans[N];
int q1[N],q2[N],q3[N],lsh[N],tre[N],sz;
inline void dfs(const int&i,const int&f)
{
    dep[i]=dep[f]+1;jmp[i][0]=f;
    for(int j=1;j<LG;++j)jmp[i][j]=jmp[jmp[i][j-1]][j-1];
    for(int j=h[i];j;j=nxt[j])if(e[j]^f)dfs(e[j],i);
}
inline void add(int&i,const int&l,const int&r,const int&x,const int&y)
{
    if(!i)i=++sz;
    if(l^r)
    {
        int mid=l+r>>1;
        if(x<=mid)add(a[i].l,l,mid,x,y);
        else add(a[i].r,mid+1,r,x,y);
        if(!a[i].l||(a[i].r&&a[a[i].r].maxn>a[a[i].l].maxn))
            a[i].maxi=a[a[i].r].maxi,a[i].maxn=a[a[i].r].maxn;
        else a[i].maxi=a[a[i].l].maxi,a[i].maxn=a[a[i].l].maxn;
    }
    else a[i].maxi=l,a[i].maxn+=y;
}
inline void onion(int&i,int&j)
{
    if(!j)return;
    if(!i){i=j;return;}
    if(!a[i].l&&!a[i].r)
    {
        a[i].maxn+=a[j].maxn;
        if(!a[i].maxn)i=0;
    }
    else
    {
        onion(a[i].l,a[j].l);onion(a[i].r,a[j].r);
        if(!a[i].l&&!a[i].r){i=0;return;}
        if(!a[i].l||(a[i].r&&a[a[i].r].maxn>a[a[i].l].maxn))
            a[i].maxi=a[a[i].r].maxi,a[i].maxn=a[a[i].r].maxn;
        else a[i].maxi=a[a[i].l].maxi,a[i].maxn=a[a[i].l].maxn;
    }
}
inline void dfs2(const int&i,const int&f)
{
    for(int j=h[i];j;j=nxt[j])if(e[j]^f)
    {
        dfs2(e[j],i);
        onion(tre[i],tre[e[j]]);
    }
    if(!tre[i])ans[i]=-1;
    else ans[i]=a[tre[i]].maxi;
}
main()
{
    read(n);read(m);
    for(int i=1,u,v;i<n;++i)
    {
        read(u);read(v);--u;--v;
        e[i]=v;nxt[i]=h[u];h[u]=i;
        e[i+n]=u;nxt[i+n]=h[v];h[v]=i+n;
    }
    dfs(0,0);
    for(int i=0;i<m;read(q1[i]),--q1[i],read(q2[i]),--q2[i],
        read(q3[i]),lsh[i]=q3[i],++i);
    sort(lsh,lsh+m);p=unique(lsh,lsh+m)-lsh;
    for(int i=0,u,v,w,x,y,l;i<m;++i)
    {
        u=q1[i];v=q2[i];w=lower_bound(lsh,lsh+p,q3[i])-lsh;
        if(!u){add(tre[v],0,p-1,w,1);continue;}
        if(!v){add(tre[u],0,p-1,w,1);continue;}
        x=u;y=v;
        if(dep[x]<dep[y])x^=y^=x^=y;
        for(int i=LG-1;i>=0;--i)if(dep[jmp[x][i]]>=dep[y])x=jmp[x][i];
        for(int i=LG-1;i>=0;--i)if(jmp[x][i]^jmp[y][i])
            x=jmp[x][i],y=jmp[y][i];
        l=(x^y?jmp[x][0]:x);
        if(l)add(tre[u],0,p-1,w,1),
            add(tre[v],0,p-1,w,1),
            add(tre[l],0,p-1,w,-1),
            add(tre[jmp[l][0]],0,p-1,w,-1);
        else add(tre[u],0,p-1,w,1),
            add(tre[v],0,p-1,w,1),
            add(tre[l],0,p-1,w,-1);
    }
    dfs2(0,0);
    for(int i=0;i<n;printf("%d\n",~ans[i]?lsh[ans[i]]:0),++i);
}

习题

P13129

vector deque

讲讲启发式合并。

f[i]=\max\limits_{j=1}^\frac i2 j+f[j]+f[i-j]

例题

P8496

摩尔投票:解决绝对众数问题。

参考实现

#include<stdio.h>
#include<deque>
#define N 1000002
using namespace std;
inline char nc()
{
    static char buf[99999],*l,*r;
    return l==r&&(r=(l=buf)+fread(buf,1,99999,stdin),l==r)?EOF:*l++;
}
inline void read(int&x)
{
    char c=nc();for(;c<'0'||'9'<c;c=nc());
    for(x=0;'0'<=c&&c<='9';x=(x<<3)+(x<<1)+(c^48),c=nc());
}
struct data
{
    int maxn,cnt;
    inline data operator+(const data&kkk)const
    {
        if(maxn==kkk.maxn)return(data){maxn,cnt+kkk.cnt};
        if(cnt>kkk.cnt)return(data){maxn,cnt-kkk.cnt};
        return(data){kkk.maxn,kkk.cnt-cnt};
    }
}tmp;
struct node
{
    data x;node*l,*r;
    inline node(){x.cnt=0;l=r=0;}
}*tre[N];
int n,q,b[N],f[N];deque<int>a[N>>1];
inline int find(const int&x){if(x^f[x])return f[x]=find(f[x]);return x;}
inline void upd(node*i)
{
    if(!i->l)i->x=i->r->x;
    else if(!i->r)i->x=i->l->x;
        else i->x=i->l->x+i->r->x;
}
inline void add(node*&i,const int&l,const int&r,const int&x)
{
    if(!i)i=new node;
    if(l==r){i->x.maxn=x;++i->x.cnt;return;}
    int mid=l+r>>1;
    if(x<=mid)add(i->l,l,mid,x);
    else add(i->r,mid+1,r,x);
    upd(i);
}
inline void del(node*&i,const int&l,const int&r,const int&x)
{
    if(l==r){--i->x.cnt;if(!i->x.cnt)i=0;return;}
    int mid=l+r>>1;
    if(x<=mid)del(i->l,l,mid,x);
    else del(i->r,mid+1,r,x);
    if(!i->l&&!i->r)i=0;
    else upd(i);
}
inline int ask(const node*i,const int&l,const int&r,const int&x)
{
    if(!i)return 0;
    if(l==r)return i->x.cnt;
    int mid=l+r>>1;
    if(x<=mid)return ask(i->l,l,mid,x);
    return ask(i->r,mid+1,r,x);
}
inline void onion(node*&i,node*&j)
{
    if(!i){i=j;return;}
    if(!j)return;
    if(i->l||i->r)onion(i->l,j->l),onion(i->r,j->r),upd(i);
    else i->x.cnt+=j->x.cnt;
}
main()
{
    read(n);read(q);
    for(int i=1,x;i<=n;++i)
    {
        read(x);a[i].resize(x);f[i]=i;
        for(int j=0;j<x;read(a[i][j]),add(tre[i],0,n+q,a[i][j]),++j);
    }
    for(int qq=q,o,x,y,z;qq--;)switch(read(o),o)
    {
        case 1:read(x);read(y);
            a[find(x)].emplace_back(y);add(tre[x],0,n+q,y);
            break;
        case 2:read(x);
            del(tre[x],0,n+q,a[find(x)].back());a[find(x)].pop_back();
            break;
        case 3:read(x);tmp.cnt=0;y=z=0;
            for(int i=0;i<x;read(b[i]),y+=a[find(b[i])].size()
                ,a[find(b[i])].size()&&(tmp=tmp+tre[b[i]]->x,1),++i);
            for(int i=0;i<x;z+=ask(tre[b[i++]],0,n+q,tmp.maxn));
            // for(int i=0;i<x;++i)
            // {
            //  for(int j=0;j<a[find(b[i])].size();++j)
            //      printf("%d ",a[find(b[i])][j]);
            //  putchar('\n');
            // }
            // printf("%d %d %d %d\n",tmp.maxn,tmp.cnt,y,z);
            if(z>y>>1)printf("%d\n",tmp.maxn);
            else printf("-1\n");
            break;
        case 4:read(x);read(y);read(z);
            onion(tre[z]=tre[y],tre[x]);
            if(a[find(x)].size()<a[find(y)].size())
                for(f[z]=find(y);a[find(x)].size();a[find(z)].push_front(
                    a[find(x)].back()),a[find(x)].pop_back());
            else for(f[z]=find(x);a[find(y)].size();a[find(z)].push_back(
                    a[find(y)].front()),a[find(y)].pop_front());
    }
}

可并堆

不讲。

平衡树

两棵树值域不交:直接连起来。

有交:启发式合并。

设小的树大小为 m,大的树大小为 n,如果使用了 finger serch,复杂度为 \mathcal O(m\log n)