北京培训记day5

高级数据结构

一、左偏树&斜堆

  orz黄源河论文

  合并,插入,删除根节点

  打标记

struct Node { int fa,l,r,w,dep } tree[Mx];
int Merge(int k1,int k2)//返回值为根节点
{
    if(k1==0||k2==0) return k1+k2;
    if(tree[k1].val<tree[k2].val) swap(k1,k2);
    tree[k1].r=Merge(tree[k1].r,k2);
    tree[tree[k1].r].fa=k1;
    if(tree[tree[k1].l].dep<tree[tree[k1].r].dep) swap(tree[k1].l,tree[k1].r);
    if(tree[k1].r==0) tree[k1].dep=0;
    else tree[k1].dep=tree[tree[k1].r].dep+1;
    return k1;
}
int del(int root)//删除根节点
{
    int k1=tree[root].l,k2=tree[root].r;
    l[root]=0;r[root]=0;fa[k1]=0;fa[k2]=0;
    return Merge(k1,k2);
}

  应用:bzoj1367【Baltic 2004】

     给定一个整数序列a1,a2,…,an,求一个不下降序列b1≤b2≤…≤bn,使得数列{ai}和{bi}的各项之差的绝对值之和最小

     首先给出结论:①bi是由多段相同的数组成的

            ②bi的每一段的数一定是ai该段的中位数

     P.S.证明我也不会QAQ,自己看论文

     solution:考虑若已经解决了前k个数的最优解(m个区间),记为w[1~m],

          新加一个点时,先将其单独作为一个区间插入w,若w[now]<w[now-1],

          则合并后两个区间,并继续判定,直到满足单调不降,最终w序列即为b序列。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
const int Mx=500010;
int n,top,ans,a[Mx],b[Mx],L[Mx],R[Mx],stk[Mx],siz[Mx];
struct Node { int fa,l,r,val,dep; } tree[Mx];
int Merge(int k1,int k2)
{
    if(k1==0||k2==0) return k1+k2;
    if(tree[k1].val<tree[k2].val) swap(k1,k2);
    tree[k1].r=Merge(tree[k1].r,k2);
    tree[tree[k1].r].fa=k1;
    if(tree[tree[k1].l].dep<tree[tree[k1].r].dep) swap(tree[k1].l,tree[k1].r);
    if(tree[k1].r==0) tree[k1].dep=0;
    else tree[k1].dep=tree[tree[k1].r].dep+1;
    return k1;
}
signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)
    {
        tree[i].val=a[i];
        stk[++top]=i;
        L[top]=R[top]=i;
        siz[top]=1;
        while(top>1&&tree[stk[top]].val<=tree[stk[top-1]].val)
        {
            R[top-1]=R[top];
            siz[top-1]+=siz[top];
            stk[top-1]=Merge(stk[top-1],stk[top]);
            top--;
            int len=R[top]-L[top]+1;
            if(len%2==0) len /= 2;
            else len = len / 2 + 1;
            while(siz[top]>len)
            {
                siz[top]--;
                stk[top]=Merge(tree[stk[top]].l, tree[stk[top]].r);
            }
        }
    }
    for(int i=1;i<=top;i++)
        for(int j=L[i];j<=R[i];j++)
            b[j]=tree[stk[i]].val;
    for(int i=1;i<=n;i++) ans+=abs(b[i]-a[i]);
    printf("%lld\n", ans);
    return 0;
}

 

二、线段树

  建树,修改,查询,lazy标记

  主席树,可持久化线段树

  //zkw线段树

  例:bzoj1146

    bzoj2653

三、平衡树

  旋转:splay

     单旋&双旋

     单旋仅在当前节点为根节点的儿子时使用

     考虑当前节点与其父亲,其父亲与其祖父的关系,分为zig-zig,zig-zag

     

     当p为根时进行zig操作

     若x为p的左儿子,则p的左子树换为x的右子树,x的右儿子换为p。(对x右旋)

     若x为p的右儿子,则p的右子树换为x的左子树,x的左儿子换为p。(对x左旋)

     

     当x,p同为左(右)儿子时进行zig-zig操作

     若同为左儿子,则先将p右旋,再将x右旋

     若同为右儿子,则先将p左旋,再将x左旋

     

     当x,p不同为左(右)儿子时进行zig-zag操作

     若p为左孩子,x为右孩子,将x先左旋再右旋

     若p为右孩子,x为左孩子,将x先右旋再左旋

#include<iostream>
using namespace std;

void pushup(int rr)
{
    t[rr].siz=t[t[rr].l].siz+t[t[rr].r].siz+t[rr].num;
}
void zig(int x)
{
    int y=t[x].fa;
    if(t[x].r)t[y].l=t[x].r,t[t[x].r].fa=y;
    else t[y].l=0;
    if(t[t[y].fa].l==y)t[t[y].fa].l=x;
    else t[t[y].fa].r=x;
    t[x].fa=t[y].fa;t[x].r=y;t[y].fa=x;
    pushup(y);
}
void zag(int x)
{
    int y=t[x].fa;
    if(t[x].l)t[y].r=t[x].l,t[t[x].l].fa=y;
    else t[y].r=0;
    if(t[t[y].fa].l==y)t[t[y].fa].l=x;
    else t[t[y].fa].r=x;
    t[x].fa=t[y].fa;t[x].l=y;t[y].fa=x;
    pushup(y);
}
void splay(int x,int tar)
{
    while(t[x].fa!=tar)
    {
        int y=t[x].fa,yy=t[y].fa;
        if(yy)
        {
            if(t[y].l==x)zig(x);
            else zag(x);
        }
        else
        {
            if(t[yy].l==y)
            {
                if(t[y].l==x)zig(y),zig(x);
                else zag(x),zig(x);
            }
            else
            {
                if(t[y].l==x)zig(x),zag(x);
                else zag(y),zag(x);
            }
        }
    }
    if(!tar)root=x;
    pushup(x);
}
void newnode(int &x,int fa,int key)
{
    x=++tot;
    t[x].pre=fa;
    t[x].siz=t[x].num=1;
    t[x].rev=t[x].l=t[x].r=0;
    t[x].key=key;
}
void build(int l,int r,int &rr,int fa)
{
    if(l>r)return;
    int mid=(l+r)/2;
    newnode(rr,fa,a[mid]);
    build(l,mid-1,t[rr].l,rr);
    build(mid+1,r,t[rr].r,rr);
    pushup(rr);
}
int main()
{
    // 1 to n a[]
    build(1,n,root,0);
    return 0;
}

     treap

     插入节点:先将节点插入,然后随机一个优先级,通过旋转使优先级满足堆性质。

     

     删除:找到节点后将其旋转到叶子上并删除

     查询:同二叉搜索树

//来源:@SiriusRen (我懒得写了QAQ) 
//poj1442:动态插入点,查询第k大 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int cnt=0,jy,a[30500],n,m,root=-1;
struct node{
    int left,right,count_left,count_right,key,priority;
}treap[30500];
void zig(int &x){
    int y=treap[x].right;
    treap[x].right=treap[y].left;
    treap[x].count_right=treap[y].count_left;
    treap[y].left=x;
    treap[y].count_left=treap[x].count_left+treap[x].count_right+1;
    x=y;
}
void zag(int &x){
    int y=treap[x].left;
    treap[x].left=treap[y].right;
    treap[x].count_left=treap[y].count_right;
    treap[y].right=x;
    treap[y].count_right=treap[x].count_left+treap[x].count_right+1;
    x=y;
}
void insert(int &x,int new_key){
    if(x==-1){
        x=cnt++;
        treap[x].left=treap[x].right=-1;
        treap[x].priority=rand();
        treap[x].key=new_key;
        treap[x].count_left=treap[x].count_right=0;
    }
    else if(new_key<treap[x].key){
        treap[x].count_left++;
        insert(treap[x].left,new_key);
        if(treap[x].priority>treap[treap[x].left].priority)zag(x);
    }
    else{
        treap[x].count_right++;
        insert(treap[x].right,new_key);
        if(treap[x].priority>treap[treap[x].right].priority)zig(x);
    }
}
int query(int &x,int k_th){
    if(treap[x].count_left+1==k_th)return treap[x].key;
    if(treap[x].count_left+1<k_th)return query(treap[x].right,k_th-treap[x].count_left-1);
    return query(treap[x].left,k_th);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=m;i++){
        scanf("%d",&jy);
        while(cnt<jy)insert(root,a[cnt+1]);
        printf("%d\n",query(root,i));
    }
}

       笛卡尔树

       后缀平衡树

  重建:替罪羊树

     在二叉搜索树的基础上拍扁重建为完全二叉树

     加点:往上判断找最靠近根的不满足(siz[l]<=0.7*siz[root]&&siz[r]<=0.7*siz[root])的点,将此子树拍扁重建

     删点:打标记,如果某棵子树被删掉的点>=siz/2,就将这棵子树重建

     查询:与二叉搜索树相同

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int Mx=100010;
struct Node { int val,cnt; }tree[Mx];
int n,point,tot,l[Mx],r[Mx],fa[Mx],siz[Mx];
int num[Mx],cnt1;
void pre(int root)
{
    if(root!=0)
    {
        pre(l[root]);
        if(tree[root].cnt!=0) num[++cnt1]=root;
        pre(r[root]);
    }
}
int tmp;
void build(int lft,int rgh,int root,int jud)
{
    int now=num[(lft+rgh)/2];
    fa[now]=root;r[now]=0;l[now]=0;siz[now]=0;
    if(jud==0) l[root]=now;
    else r[root]=now;
    if(lft<(lft+rgh)/2) build(lft,(lft+rgh)/2-1,now,0);
    if(rgh>(lft+rgh)/2) build((lft+rgh)/2+1,rgh,now,1);
    siz[now]++;fa[siz[now]]+=siz[now];
}
void rebuild(int root,int jud)
{
    cnt1=0;
    pre(root);
    build(1,cnt1,fa[root],jud);
}
void add(int num,int root)
{
    if(num==tree[root].val)
    {
        tree[root].cnt++;
        int temp=root;
        while(fa[temp]!=0)
        {
            temp=fa[temp];
            siz[temp]++;
        }
    }
    else if(num<tree[root].val)
    {
        if(l[root]==0)
        {
            tree[++tot].val=num;tree[tot].cnt=1;
            l[root]=tot;fa[tot]=root;
            siz[tot]=1;
            int temp=tot,temp1=0;
            while(fa[temp]!=0)
            {
                temp=fa[temp];
                siz[temp]++;
                if((double)siz[l[temp]]>0.7*(double)siz[temp]||(double)siz[r[temp]]>0.7*(double)siz[temp]) temp1=temp;
            }
            if(temp1!=0)
            {
                if(l[fa[temp1]]==temp1) rebuild(temp1,0);
                else rebuild(temp1,1);
            }
        }
        else add(num,l[root]);
    }
    else
    {
        if(r[root]==0)
        {
            tree[++tot].val=num;tree[tot].cnt=1;
            r[root]=tot;fa[tot]=root;
            siz[tot]=1;
            int temp=tot,temp1=0;
            while(fa[temp]!=0)
            {
                temp=fa[temp];
                siz[temp]++; 
                if((double)siz[l[temp]]>0.7*(double)siz[temp]||(double)siz[r[temp]]>0.7*(double)siz[temp]) temp1=temp;
            }
            if(temp1!=0)
            {
                if(l[fa[temp1]]==temp1) rebuild(temp1,0);
                else rebuild(temp1,1);
            }
        }
        else add(num,r[root]);
    }
}
int cntdel[Mx];
void cleardel(int root)
{
    cntdel[root]=0;
    if(l[root]!=0) cleardel(l[root]);
    if(r[root]!=0) cleardel(r[root]);
}
void del(int num)
{
    int root=0,tmp=0;
    while(tree[root].val!=num)
    {
        if(tree[root].val<num) root=r[root];
        else root=l[root];
    }
    tree[root].cnt--;cntdel[root]++;siz[root]--;
    while(fa[root]!=0)
    {
        root=fa[root];cntdel[root]++;siz[root]--;
        if((double) cntdel[root]>=(double) siz[root]/2) tmp=root;
    }
    if(tmp!=0)
    {
        if(l[fa[tmp]]==tmp) rebuild(tmp,0);
        else rebuild(tmp,1);
        cleardel(tmp);
    }
}
void search_rank(int num,int root,int tmp)
{
    if(num==tree[root].val) printf("%d\n",tmp+siz[l[root]]+1);
    else if(num<tree[root].val) search_rank(num,l[root],tmp);
    else search_rank(num,r[root],tmp+siz[l[root]]+1);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int jud,a;scanf("%d%d",&jud,&a);
        if(jud==0) add(a,0);
        else if(jud==1) del(a);
        else search_rank(a,r[0],0);
    }
    return 0;
}

  分裂合并:FHQ treap

四、树套树

  线段树套线段树

  线段树套平衡树 (区间第k小)

  树状数组套主席树

  替罪羊树套主席树

五、莫队算法

  例:bzoj2038【小z的袜子】

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
const int Mx=50010;
struct Node { int l,r,num; } que[Mx];
bool cmp1 (Node a,Node b) { return a.l<b.l; }
bool cmp2 (Node a,Node b) { return a.r<b.r; }
int n,m,c[Mx],num[Mx],ans[Mx],ans1[Mx][2];
inline int gcd (int a,int b) { int tmp; while(a>0) tmp=b%a,b=a,a=tmp;  return b; }
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
    for(int i=1;i<=m;i++) { scanf("%lld%lld",&que[i].l,&que[i].r);if(que[i].l>que[i].r) swap(que[i].l,que[i].r); }
    for(int i=1;i<=m;i++) que[i].num=i;
    sort(que+1,que+1+m,cmp1);
    for(int i=1;i<=m;i+=sqrt(m)) sort(que+i,que+min(m,i+(int)sqrt(m)),cmp2);
    for(int i=1;i<=m;i++)
    {
        if(i%(int)sqrt(m)==1||i==1)
        {
            memset(num,0,sizeof(num));
            for(int j=que[i].l;j<=que[i].r;j++) num[c[j]]++;
            for(int j=0;j<=n;j++) ans[i]+=num[j]*(num[j]-1)/2;
        }
        else
        {
            for(int j=que[i-1].l,to=que[i].l;j!=to;)
            {
                if(j<to) ans[i]-=num[c[j]]-1,num[c[j]]--,j++;
                else ans[i]+=num[c[j-1]],num[c[j-1]]++,j--;
            }
            for(int j=que[i-1].r,to=que[i].r;j!=to;)
            {
                if(j<to) ans[i]+=num[c[j+1]],num[c[j+1]]++,j++;
                else ans[i]-=num[c[j]]-1,num[c[j]]--,j--;
            }
            ans[i]+=ans[i-1];
        }    
        int div=gcd(ans[i],(que[i].r-que[i].l+1)*(que[i].r-que[i].l)/2);
        if(que[i].r==que[i].l||ans[i]==0) ans1[que[i].num][0]=0,ans1[que[i].num][1]=1;
        else ans1[que[i].num][0]=ans[i]/div,ans1[que[i].num][1]=(que[i].r-que[i].l+1)*(que[i].r-que[i].l)/(div*2);
    }
    for(int i=1;i<=m;i++) printf("%lld/%lld\n",ans1[i][0],ans1[i][1]);
    return 0;
}
posted @ 2016-12-17 12:27  Czarina  阅读(274)  评论(0编辑  收藏  举报