[WC 2015复习](一)中级数据结构与分治算法

都是比较简单SB的东西,求各位去WC的神犇勿喷。

1、Treap

(1)[BZOJ 3224]Tyvj 1728 普通平衡树

http://www.lydsy.com/JudgeOnline/problem.php?id=3224

基础的平衡树操作。对序列中单点修改、求第k小数、求某个数的排名、查询某个数的前驱后继。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
 
#define MAXN 100010
#define lson node[o].lc
#define rson node[o].rc
 
using namespace std;
 
struct Treap
{
    struct Node
    {
        int lc,rc;
        int val,key; //val=该点的键值(平衡二叉树用),key=该点的权值(Heap用)
        int cnt; //该点对应的相同数字个数
        int size; //该点对应的子树大小
    }node[MAXN];
 
    int nCount,root; //结点总个数、根节点编号
 
    void pushup(int o) //维护结点o
    {
        node[o].size=node[lson].size+node[rson].size+node[o].cnt;
    }
     
    void rotateR(int &o) //右旋结点o
    {
        int tmp=lson;
        lson=node[tmp].rc;
        node[tmp].rc=o;
        node[tmp].size=node[o].size;
        pushup(o);
        o=tmp;
    }
 
    void rotateL(int &o) //左旋结点o
    {
        int tmp=rson;
        rson=node[tmp].lc;
        node[tmp].lc=o;
        node[tmp].size=node[o].size;
        pushup(o);
        o=tmp;
    }
     
    void insert(int &o,int x) //在根节点为o的子树下插入键值为x的点
    {
        if(o==0) //空树,建立新的节点
        {
            o=++nCount;
            node[o].size=node[o].cnt=1;
            node[o].key=rand()*rand();
            node[o].val=x;
            return;
        }
        node[o].size++;
        if(x<node[o].val) //x比o的键值小,往左子树插入
        {
            insert(lson,x);
            if(node[o].key>node[lson].key) //维护小根堆的性质
                rotateR(o);
        }
        else if(x>node[o].val) //x比o的键值大,往右子树插入
        {
            insert(rson,x);
            if(node[o].key>node[rson].key) //维护小根堆的性质
                rotateL(o);
        }
        else //x与o的键值相同
            node[o].cnt++;
    }
 
    void del(int &o,int x) //在根节点为o的子树下删除键值为x的点
    {
        if(o==0) return;
        if(node[o].val==x) //o的键值就是x
        {
            if(node[o].cnt>1) //o对应的相同数字不止一个
            {
                node[o].size--;
                node[o].cnt--;
            }
            //o只对应一个数字,删掉这个数字,结点o就没了
            else if(lson==0||rson==0) //o只有一个儿子
                o=lson+rson;
            else if(node[lson].key<node[rson].key) //左子树权值小
            {
                rotateR(o); //把o旋到左子树里去
                del(o,x);
            }
            else //右子树权值小
            {
                rotateL(o); //把o旋到右子树里去
                del(o,x);
            }
            return;
        }
        node[o].size--;
        if(x<node[o].val) del(lson,x);
        else del(rson,x);
    }
 
    int getRank(int o,int x) //求键值x在树o中的排名
    {
        if(o==0) return 0;
        if(node[o].val==x) return node[lson].size+1; //o的键值就是x
        if(node[o].val<x) return node[lson].size+node[o].cnt+getRank(rson,x);
        return getRank(lson,x);
    }
 
    int getNumFromRank(int o,int kth) //求树中第k小的数
    {
        if(o==0) return 0;
        if(kth<=node[lson].size) return getNumFromRank(lson,kth);
        else if(kth>node[lson].size+node[o].cnt) return getNumFromRank(rson,kth-node[lson].size-node[o].cnt);
        else return node[o].val;
    }
 
    void getBefore(int o,int x,int &ans) //在树o中求键值为x的结点的前驱
    {
        if(o==0) return;
        if(node[o].val<x)
        {
            ans=node[o].val;
            getBefore(rson,x,ans);
        }
        else getBefore(lson,x,ans);
    }
 
    void getAfter(int o,int x,int &ans) //在树o中求键值为x的结点的后继
    {
        if(o==0) return;
        if(node[o].val>x)
        {
            ans=node[o].val;
            getAfter(lson,x,ans);
        }
        else getAfter(rson,x,ans);
    }
}treap;
 
int main()
{
    srand(23333);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int cmd,x;
        scanf("%d%d",&cmd,&x);
        if(cmd==1) //插入操作
            treap.insert(treap.root,x);
        else if(cmd==2) //删除操作
            treap.del(treap.root,x);
        else if(cmd==3) //求排名
            printf("%d\n",treap.getRank(treap.root,x));
        else if(cmd==4) //已知排名求数字
            printf("%d\n",treap.getNumFromRank(treap.root,x));
        else if(cmd==5) //求前驱
        {
            int ans=0;
            treap.getBefore(treap.root,x,ans);
            printf("%d\n",ans);
        }
        else if(cmd==6) //求后继
        {
            int ans=0;
            treap.getAfter(treap.root,x,ans);
            printf("%d\n",ans);
        }
    }
    return 0;
}



2、Splay

(1)[BZOJ 3223]Tyvj 1729 文艺平衡树

http://www.lydsy.com/JudgeOnline/problem.php?id=3223

要求支持对序列进行翻转操作,并输出最终的序列。给Splay打上翻转标记rev即可。建议将长度为n的序列1~n转变为长度为n+2的序列1~n+2,最左边的数字1和最右边的数字n+2起保护作用,真正的被操作序列是2~n+1。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
 
#define MAXN 100100
#define lson node[o].ch[0]
#define rson node[o].ch[1]
 
using namespace std;
 
struct Splay
{
    struct node
    {
        int fa; //父结点
        bool rev; //翻转标记
        int size; //子树大小
        int val; //结点键值
        int ch[2]; //左右儿子
    }node[MAXN];
 
    int nCount,root; //开的结点个数、根节点
    int id[MAXN]; //初始时的序列
     
    void pushup(int o) //上传size标记
    {
        node[o].size=node[lson].size+node[rson].size+1;
    }
 
    void pushdown(int o) //下传rev标记
    {
        if(node[o].rev)
        {
            swap(lson,rson); //交换左右儿子
            node[lson].rev^=1;
            node[rson].rev^=1;
            node[o].rev=0;
        }
    }
 
    void build(int L,int R,int fa) //建立区间[L,R]的结点,该结点对应子树的父亲是fa
    {
        if(L>R) return;
        int now=L,last=fa;
        if(L==R)
        {
            node[L].fa=fa;
            node[L].size=1;
            node[L].val=id[L];
            if(L<fa) node[fa].ch[0]=L;
            else node[fa].ch[1]=L;
            return;
        }
        int M=(L+R)>>1;
        build(L,M-1,M);
        build(M+1,R,M);
        node[M].fa=fa;
        node[M].val=id[M];
        pushup(M);
        if(M<fa) node[fa].ch[0]=M;
        else node[fa].ch[1]=M;
    }
 
    void rotate(int x,int &k) //把x旋转到其上一层去,不能超过k的高度
    {
        int y=node[x].fa;
        int z=node[y].fa;
        int p,q;
        if(node[y].ch[0]==x) p=0;
        else p=1;
        q=p^1;
        if(y==k) k=x;
        else
        {
            if(node[z].ch[0]==y)
                node[z].ch[0]=x;
            else
                node[z].ch[1]=x;
        }
        node[x].fa=z;
        node[y].fa=x;
        node[node[x].ch[q]].fa=y;
        node[y].ch[p]=node[x].ch[q];
        node[x].ch[q]=y;
        pushup(y);
        pushup(x);
    }
 
    void splay(int x,int &k) //把x伸展到点k去
    {
        while(x!=k)
        {
            int y=node[x].fa;
            int z=node[y].fa;
            if(y!=k)
            {
                if((node[y].ch[0]==x)==(node[z].ch[0]==y)) rotate(y,k);
                else rotate(x,k);
            }
            rotate(x,k);
        }
    }
 
    int getKth(int o,int kth) //在子树o中找第k小数
    {
        pushdown(o);
        if(node[lson].size+1==kth) return node[o].val; //o就是第k小数
        else if(node[lson].size>=kth) return getKth(lson,kth);
        else return getKth(rson,kth-node[lson].size-1);
    }
 
    int reverse(int L,int R) //翻转区间(L,R)
    {
        int x=getKth(root,L),y=getKth(root,R+2);
        splay(x,root);
        splay(y,node[x].ch[1]);
        int z=node[y].ch[0]; //z子树对应区间(L,R)
        node[z].rev^=1;
    }
}spt;
 
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n+2;i++)
        spt.id[i]=i;
    spt.build(1,n+2,0);
    spt.root=(n+3)>>1;
    for(int i=1;i<=m;i++)
    {
        int L,R;
        scanf("%d%d",&L,&R);
        spt.reverse(L,R);
    }
    for(int i=2;i<=n+1;i++)
        printf("%d ",spt.getKth(spt.root,i)-1);
    return 0;
}


3、树链剖分

(1)[BZOJ 1036][ZJOI 2008]树的统计Count

http://www.lydsy.com/JudgeOnline/problem.php?id=1036

要求支持对一棵有点权、无边权的静态无根树进行修改点权操作,并支持查询任意两点之间最短路上的最大点权和点权和。

裸树链剖分+套线段树或者BIT。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
 
#define MAXN 31000
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&(-(x)))
 
using namespace std;
 
struct edge
{
    int u,v,next;
}edges[MAXN*2];
 
int head[MAXN],nCount=0;
 
void AddEdge(int U,int V) //添加有向边U->V
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].next=head[U];
    head[U]=nCount;
}
 
void add(int U,int V) //添加无向边U-V
{
    AddEdge(U,V);
    AddEdge(V,U);
}
 
int depth[MAXN],fa[MAXN],size[MAXN],son[MAXN]; //depth[i]=点i的深度,fa[i]=树中点i的父亲,size[i]=子树i的大小,son[i]=点i的重儿子
int hash[MAXN],num=0; //hash[i]=点i在线段树中的编号
int top[MAXN]; //top[i]=点i所属重链的祖先
int w[MAXN];
 
void DFS(int u,int dep) //第一次DFS找重儿子
{
    size[u]=1;
    son[u]=0;
    depth[u]=dep;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(v==fa[u]) continue;
        fa[v]=u;
        DFS(v,dep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}
 
void BuildTree(int u,int anc) //第二次DFS找重链
{
    hash[u]=++num;
    top[u]=anc;
    if(son[u]) BuildTree(son[u],anc);
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(v==fa[u]||v==son[u]) continue;
        BuildTree(v,v);
    }
}
 
struct SegmentTree
{
    int maxv[4*MAXN],sumv[4*MAXN];
 
    void pushup(int o) //上传标记
    {
        sumv[o]=sumv[o*2]+sumv[o*2+1];
        maxv[o]=max(maxv[o*2],maxv[o*2+1]);
    }
 
    void update(int pos,int x,int L,int R,int o) //当前结点o对应区间[L,R],将pos位置的数字修改为x
    {
        if(L==R)
        {
            maxv[o]=sumv[o]=x;
            return;
        }
        int M=(L+R)>>1;
        if(pos<=M) update(pos,x,L,M,o*2);
        else update(pos,x,M+1,R,o*2+1);
        pushup(o);
    }
 
    int QueryMax(int ql,int qr,int L,int R,int o) //求[ql,qr]最值
    {
        if(ql==L&&qr==R) return maxv[o];
        int M=(L+R)>>1;
        if(qr<=M) return QueryMax(ql,qr,L,M,o*2);
        else if(ql>M) return QueryMax(ql,qr,M+1,R,o*2+1);
        else return max(QueryMax(ql,M,L,M,o*2),QueryMax(M+1,qr,M+1,R,o*2+1));
    }
 
    int QuerySum(int ql,int qr,int L,int R,int o) //求[ql,qr]区间和
    {
        if(ql==L&&qr==R) return sumv[o];
        int M=(L+R)>>1;
        if(qr<=M) return QuerySum(ql,qr,L,M,o*2);
        else if(ql>M) return QuerySum(ql,qr,M+1,R,o*2+1);
        else return QuerySum(ql,M,L,M,o*2)+QuerySum(M+1,qr,M+1,R,o*2+1);
    }
}segtree;
 
int QueryMax(int a,int b,int n) //求a到b的路径上的最大点权,整棵树有n个点
{
    int ta=top[a],tb=top[b],ans=-INF;
    while(ta!=tb)
    {
        if(depth[ta]<depth[tb])
        {
            swap(ta,tb);
            swap(a,b);
        }
        ans=max(ans,segtree.QueryMax(hash[ta],hash[a],1,n,1));
        a=fa[ta]; //爬到上一个链上
        ta=top[a];
    }
    if(depth[a]>depth[b]) swap(a,b);
    return max(ans,segtree.QueryMax(hash[a],hash[b],1,n,1));
}
 
int QuerySum(int a,int b,int n) //求a到b的路径上的点权和,整棵树有n个点
{
    int ta=top[a],tb=top[b],ans=0;
    while(ta!=tb)
    {
        if(depth[ta]<depth[tb])
        {
            swap(ta,tb);
            swap(a,b);
        }
        ans+=segtree.QuerySum(hash[ta],hash[a],1,n,1);
        a=fa[ta]; //爬到上一个链上
        ta=top[a];
    }
    if(depth[a]>depth[b]) swap(a,b);
    return ans+=segtree.QuerySum(hash[a],hash[b],1,n,1);
}
 
int main()
{
    memset(head,-1,sizeof(head));
    int n;
    char cmd[10];
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    DFS(1,1);
    BuildTree(1,1);
    for(int i=1;i<=n;i++)
        segtree.update(hash[i],w[i],1,n,1);
    int q;
    scanf("%d",&q);
    while(q--)
    {
        int a,b;
        scanf("%s%d%d",cmd,&a,&b);
        if(cmd[1]=='M') //QMAX
            printf("%d\n",QueryMax(a,b,n));
        else if(cmd[1]=='S') //QSUM
            printf("%d\n",QuerySum(a,b,n));
        else segtree.update(hash[a],b,1,n,1);
    }
    return 0;
}


4、Link-Cut Tree

(1)[BZOJ 2631]tree

http://www.lydsy.com/JudgeOnline/problem.php?id=2631

一道很好的LCT练手题。注意打标记的问题,由于此题中同时存在区间乘和区间加标记,因此要注意如何更新这两个标记,我们可以约定同时存在区间乘和区间加标记时,先区间同时乘,然后再进行区间加。如果一个区间要乘上mulv后再加上addv,而其原有的区间和为sumv[o],原有的区间乘和区间加标记为mul[o]和add[o],那么此次修改操作后这个区间的区间和应为[(sumv[o]*mul[o]+add[o])*mulv+addv]=sumv[o]*(mul[o]*mulv)+(add[o]*mulv+addv),因此该区间新的区间乘标记为(mul[o]*mulv),新的区间加标记为(add[o]*mulv+addv)

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 102000
#define MOD 51061
#define lson ch[o][0]
#define rson ch[o][1]

using namespace std;

typedef unsigned int uint;

struct LinkCutTree
{
	int nCount; //开的结点个数
	int ch[MAXN][2],fa[MAXN]; //Splay的儿子标记
	bool rev[MAXN]; //Splay的翻转标记
	int size[MAXN]; //Splay的子树大小标记
	uint sum[MAXN],mul[MAXN],add[MAXN],val[MAXN]; //Splay的区间和标记、区间乘法标记、区间加法标记、本节点的值
	int q[MAXN],top; //伸展时用的栈

	bool isRoot(int x) //判断x是否是Splay的根节点
	{
		return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
	}

	void pushup(int o) //上传标记操作
	{
		sum[o]=sum[lson]+sum[rson]+val[o];
		sum[o]%=MOD;
		size[o]=size[lson]+size[rson]+1;
		size[o]%=MOD; //????
	}

	void cal(int o,int mulv,int addv) //点o对应的区间先乘上mulv后加上addv
	{
		if(!o) return;
		val[o]=(val[o]*mulv+addv)%MOD;
		sum[o]=(sum[o]*mulv+addv*size[o])%MOD;
		add[o]=(add[o]*mulv+addv)%MOD;
		mul[o]=(mul[o]*mulv)%MOD;
	}

	void pushdown(int o) //下传add、mul、rev标记
	{
		if(rev[o]) //需要翻转o对应区间
		{
			rev[o]^=1;
			rev[lson]^=1;
			rev[rson]^=1;
			swap(lson,rson);
		}
		if(mul[o]!=1||add[o]!=0) //本节点的mul和add标记没清
		{
			cal(lson,mul[o],add[o]);
			cal(rson,mul[o],add[o]);
		}
		mul[o]=1;
		add[o]=0;
	}

	void rotate(int x) //旋转x到其上一层去
	{
		int y=fa[x],z=fa[y],p,q;
		if(ch[y][0]==x) p=0; else p=1;
		q=p^1;
		if(!isRoot(y))
		{
			if(ch[z][0]==y) ch[z][0]=x;
			else ch[z][1]=x;
		}
		fa[x]=z;
		fa[y]=x;
		fa[ch[x][q]]=y;
		ch[y][p]=ch[x][q];
		ch[x][q]=y;
		pushup(y);
		pushup(x);
	}

	void splay(int x) //将x旋转到根节点上去
	{
		top=0;
		q[++top]=x;
		for(int i=x;!isRoot(i);i=fa[i])
			q[++top]=fa[i];
		for(int i=top;i;i--)
			pushdown(q[i]);
		while(!isRoot(x))
		{
			int y=fa[x],z=fa[y];
			if(!isRoot(y))
			{
				if((ch[y][0]==x)==(ch[z][0]==y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
	}
	void access(int x) //打通x到根节点的偏爱路径
	{
		int tmp=0;
		while(x)
		{
			splay(x);
			ch[x][1]=tmp;
			pushup(x);
			tmp=x;
			x=fa[x];
		}
	}

	void makeroot(int x) //使x成为根
	{
		access(x);
		splay(x);
		rev[x]^=1;
	}

	void link(int x,int y) //连接x、y,使得y成为x的父亲
	{
		makeroot(x);
		fa[x]=y;
	}

	void cut(int x,int y) //断开x、y的连接,原来y是x的父亲
	{
		makeroot(x);
		access(y);
		splay(y);
		ch[y][0]=fa[x]=0;
	}
}lct;

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) //初始的树
	{
		int u,v;
		scanf("%d%d",&u,&v);
		lct.link(u,v);
	}
	for(int i=1;i<=n;i++)
		lct.val[i]=lct.sum[i]=lct.mul[i]=lct.size[i]=1;
	for(int i=1;i<=m;i++)
	{
		char cmd[10];
		int x,y,addv,mulv;
		scanf("%s%d%d",cmd,&x,&y);
		if(cmd[0]=='+') //x到y的路径上的点权统一加上addv
		{
			scanf("%d",&addv);
			lct.makeroot(x);
			lct.access(y);
			lct.splay(y);
			lct.cal(y,1,addv);
		}
		else if(cmd[0]=='-') //断开x和y的连接,重新连新的边
		{
			lct.cut(x,y);
			scanf("%d%d",&x,&y);
			lct.link(x,y);
		}
		else if(cmd[0]=='*') //x到y的路径上的点权统一乘上addv
		{
			scanf("%d",&mulv);
			lct.makeroot(x);
			lct.access(y);
			lct.splay(y);
			lct.cal(y,mulv,0);
		}
		else if(cmd[0]=='/') //求x到y的路径上的点权和
		{
			lct.makeroot(x);
			lct.access(y);
			lct.splay(y);
			printf("%d\n",lct.sum[y]);
		}
	}
	return 0;
}


5、点分治

6、CDQ分治

7、CDQ重构图

8、莫队算法

9、分块

你可能感兴趣的:([WC 2015复习](一)中级数据结构与分治算法)