三个半小时考试,三个小时调试-----巨容易写炸的nlogn数据结构专题

1.树状数组

         这个是最最最简单的nlogn的数据结构啦,非常好写也不容易写错(就算写错也比较容易检查)

         相比其他bt的数据结构,树状数组的代码非常简洁,就是c数组有点抽象,晦涩难懂

         主要用来单点修改,区间查询。也可以通过差分来进行区间修改和区间查询,但一般区间修改一般不止加加减减这么简单啦,所以区间修改还是线段树会比较常用。

         总体还是很easy的,不多bb直接粘代码

    

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <queue>
#define maxn 600000
#define inf 0x7fffff
using namespace std;

int n,m,x,y,k,op;
int a[maxn],c[maxn];

int read()
{
	int xx=0,kk=1;char p=' ';
	while(!isdigit(p)){p=getchar();if(p=='-')kk=-1;}
	while(isdigit(p)){xx=xx*10+p-'0';p=getchar();}
	return kk*xx;
}

int lowbit(int x)
{
	return x&-x;
}

void add(int x,int k)
{
	while(x<=n)
	{
		c[x]+=k;
		x+=lowbit(x);
	}
}

int query(int x)
{
	int ans=0;
	while(x)
	{
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;++i) a[i]=read(),add(i,a[i]);	//注意一开始给c数组赋值
	for(int j=1;j<=m;++j)
	{
		op=read();x=read();
		switch(op)
		{
			case 1:k=read();add(x,k);break;
			case 2:y=read();printf("%lld\n",query(y)-query(x-1));break;
		}
	}
	return 0;
}

2.线段树

         又臭又长的数据结构的开端,一开始觉得线段树好长啊,好难写啊,好烦啊怎么这么多函数啊,啊我怎么又写炸啦。现在回过头来看线段树,真的是非常良心的一个数据结构啦!

        线段树的用途很多,当然很多都是想不到的(现在出题人真的丧心病狂,什么神学操作都有= =)

        相比起treap和splay线段树真的是太好写了(暴风哭泣),比起树状数组又好理解并且实用性广一些

        也是直接上代码啦~

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <queue>
#define maxn 800000+10
#define inf 0x7fffff
using namespace std;

int n,m,l,r,op;
int ll[maxn],rr[maxn];
long long k,a[maxn],w[maxn],tag[maxn];

long long read()
{
	long long xx=0,kk=1;char p=' ';
	while(!isdigit(p)){p=getchar();if(p=='-')kk=-1;}
	while(isdigit(p)){xx=xx*10+p-'0';p=getchar();}
	return kk*xx;
}

void pushdown(int num)
{
	tag[num<<1]+=tag[num];
	tag[num<<1|1]+=tag[num];
	w[num<<1]+=tag[num]*(rr[num<<1]-ll[num<<1]+1);//打tag的时候顺便修改w,虽然不知道为什么,但这样做貌似在递归时会方便一点点
	w[num<<1|1]+=tag[num]*(rr[num<<1|1]-ll[num<<1|1]+1);
	tag[num]=0;
}

void build(int l,int r,int num)
{
	ll[num]=l,rr[num]=r;
	if(l==r){w[num]=a[l];return;}
	int mid=(l+r)>>1;
	build(l,mid,num<<1);
	build(mid+1,r,num<<1|1);
	w[num]=w[num<<1]+w[num<<1|1];
}

void add(int l,int r,int k,int num)
{
	if(ll[num]>r||rr[num]<l)return;
	if(ll[num]>=l&&rr[num]<=r)
	{
		tag[num]+=k;
		w[num]+=k*(rr[num]-ll[num]+1);
		return;
	}
	pushdown(num);//注意修改时要pushdown标记,因为要靠子节点的w来修改当前节点的w
	add(l,r,k,num<<1);
	add(l,r,k,num<<1|1);
	w[num]=w[num<<1]+w[num<<1|1];
}

long long query(int l,int r,int num)
{
	if(ll[num]>r||rr[num]<l)return 0;
	if(ll[num]>=l&&rr[num]<=r)return w[num];
	pushdown(num);
	return query(l,r,num<<1)+query(l,r,num<<1|1);
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)a[i]=read();
	build(1,n,1);
	for(int j=1;j<=m;++j)
	{
		op=read();l=read();r=read();
		switch(op)
		{
			case 1:{k=read();add(l,r,k,1);break;}
			case 2:{printf("%lld\n",query(l,r,1));break;}
		}
	}
	return 0;
}

3.权值线段树

         听说是个黑科技,貌似能代替treap和splay的弱者福音??(但蒟蒻我还是打不来

4.带旋treap

         虽说是省选内容但是现在noip基本都有涉及,为了达成万水水的要求当然还是要熟练掌握。

         一开始是照着石学姐的代码码的,懵逼中把模板题水了过去。现在回过头来好好理解了一下代码,加入了一些自己的理解(还是自己码的代码看起来舒服qvq)打算能像xio姐一样十分钟一个板子

          自己的理解都在注释中啦,感觉还是比较清晰吧qvq

#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define ll long long
#define maxn 200000+5
#define inf 0x7fffffff
using namespace std;

int opt,n,x,tot,root;
int son[maxn][2],rnd[maxn],cnt[maxn],val[maxn],sz[maxn];

int read()
{
    int xx=0,kk=1;char p=' ';
    while(!isdigit(p)){p=getchar();if(p=='-')kk=-1;}
    while(isdigit(p)){xx=xx*10+p-'0';p=getchar();}
    return xx*kk;
}

void update(int pos)
{
	sz[pos]=sz[son[pos][0]]+sz[son[pos][1]]+cnt[pos];//sz是该节点及其下面子节点的数的个数之和,cnt是等于当前节点的值的数的个数 
}

void rotate(int &pos,int p)//p为1时左旋(将右儿子转到父节点),p为0时右旋(将左儿子转到父节点) 
{
	int s=son[pos][p];
	sz[s]=sz[pos];
	son[pos][p]=son[s][p^1];
	son[s][p^1]=pos;
	update(pos);
	pos=s;
} 

void insert(int &pos,int x)
{
	if(!pos)
	{
		pos=++tot;
		val[pos]=x;//val使treap维持二叉树的性质(权值 右>中>左 
		rnd[pos]=rand();//rnd使treap维持堆的性质(使树深度尽可能变成logn 
	}
	sz[pos]++;
	if(val[pos]==x){cnt[pos]++;return;}
	int p=x>val[pos];
	insert(son[pos][p],x);
	if(rnd[son[pos][p]]<rnd[pos]) rotate(pos,p);
}

void del(int &pos,int x)
{
	if(!pos)return;
	if(val[pos]==x)
	{
		if(cnt[pos]>1){sz[pos]--;cnt[pos]--;return;}
		if(!(sz[son[pos][0]]*sz[son[pos][1]])){pos=son[pos][0]+son[pos][1];return;}
		int p=rnd[son[pos][0]]>rnd[son[pos][1]];//将左右儿子中优先级小的转到父节点 
		rotate(pos,p);sz[pos]--;
		del(son[pos][p^1],x);
	}
	else{sz[pos]--;del(son[pos][x>val[pos]],x);}
}

int qrank(int pos,int x)
{
	if(val[pos]==x)return sz[son[pos][0]]+1;
	if(x<val[pos])return qrank(son[pos][0],x);
	return sz[son[pos][0]]+cnt[pos]+qrank(son[pos][1],x);
}

int qnum (int pos,int x)
{
	if(x>sz[son[pos][0]]&&x<=sz[son[pos][0]]+cnt[pos])return val[pos];//也可以写成x<=sz[pos]-sz[son[pos][1]]; 
	if(x<=sz[son[pos][0]])return qnum(son[pos][0],x);
	return qnum(son[pos][1],x-sz[son[pos][0]]-cnt[pos]); 
}

int qpre(int pos,int x)
{
	if(!pos)return -inf;
	if(x>val[pos])return max(val[pos],qpre(son[pos][1],x));//将符合不大于x的数都进行比较,最大值就是前驱前驱 
	else return qpre(son[pos][0],x); 
}

int qnxt(int pos,int x)
{
	if(!pos)return inf;
	if(x<val[pos])return min(val[pos],qnxt(son[pos][0],x));//同上 
	else return qnxt(son[pos][1],x);
}

int main()
{
	srand(time(NULL));
	n=read();
	for(int i=1;i<=n;++i)
	{
		opt=read();x=read();
		switch(opt)
		{
			case 1:insert(root,x);break;
			case 2:del(root,x);break;
			case 3:printf("%d\n",qrank(root,x));break;
			case 4:printf("%d\n",qnum(root,x));break;
			case 5:printf("%d\n",qpre(root,x));break;
			case 6:printf("%d\n",qnxt(root,x));break;
		}
	}
	return 0; 
}

5.无旋treap

         貌似和带旋的差不多,为了巩固treap应该还是会找个时间学学的qwq

6.splay

         splay更是懵中带懵,跟着大佬的代码码了几遍,有点点自己的理解了,但要自己实现目前还有点点困难qwq

         要注意的是一颗splay很难同时实现翻转的插入,删除操作的。因为进行翻转操作时是打的tag并没有真正的翻转,只有在输出的时候才下放标记,下放标记的同时又破坏了二叉树的性质。但听机房大佬说貌似可以存个时间点,然后进行一些骚操作(太强啦!),反正我是不会啦!

        splay中很多小细节很容易写炸,需要多加练习

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define maxn 100000+10
using namespace std;

int n,m,l,r,root,tot;
int son[maxn][2],sz[maxn],fa[maxn],w[maxn],mark[maxn];

int read()
{
	int xx=0,kk=1;char p=' ';
	while(!isdigit(p)){p=getchar();if(p=='-') kk=-1;}
	while(isdigit(p)){xx=xx*10+p-'0';p=getchar();}
	return kk*xx;
}

void init(int n,int x,int f)
{
	sz[n]=1,w[n]=x,fa[n]=f;
}

void pushup(int x)
{
	sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;//由于模板题中没有重复元素,所以直接+1就ok了,如果重复的话此处把1改为cnt[x] 
}

void pushdown(int x)
{
	if(!mark[x])return;
	mark[son[x][0]]^=1;
	mark[son[x][1]]^=1;
	mark[x]=0;
	swap(son[x][0],son[x][1]);//交换左右儿子的下标 
}

void rotate(int x)//注意赋值顺序!! 
{
	int pre=fa[x],ppre=fa[pre];
	int pos=son[pre][1]==x;
	int ppos=son[ppre][1]==pre;
	son[ppre][ppos]=x;fa[x]=ppre;
	son[pre][pos]=son[x][pos^1];
	fa[son[x][pos^1]]=pre;
	son[x][pos^1]=pre;fa[pre]=x;//这句话应该放在最后面,因为会影响前面的赋值操作 
	pushup(pre);pushup(x);
}

void splay(int x,int goal)
{
	while(fa[x]!=goal)
	{
		int pre=fa[x],ppre=fa[pre];
		if(ppre!=goal) 
		    son[ppre][1]==pre^son[pre][1]==x?rotate(x):rotate(pre);//该节点机器父节点都为左节点或右节点时先转父节点再转该节点,不知道为什么==,可能是为了保持平衡吧ww 
		rotate(x);
	}
	if(!goal) root=x;
}

void insert(int x)
{
	int u=root,pre=0;
	while(u) pre=u,u=son[u][x>w[u]];
	u=++tot;
	if(pre)son[pre][x>w[pre]]=u;
	init(u,x,pre);
	splay(u,0);
}

int rank(int x)
{
	int u=root;
	while(1)
	{
		pushdown(u);
		if(sz[son[u][0]]>=x)u=son[u][0];
		else if(sz[son[u][0]]+1==x)return u;
		else x-=sz[son[u][0]]+1,u=son[u][1];
	}
}

void work(int l,int r)
{
	l=rank(l);r=rank(r+2);
	splay(l,0);splay(r,l);
	mark[son[son[root][1]][0]]^=1;//在大于l小于r+2的节点上打标记,最后输出结果时要-1 
}

void print(int u)
{
	pushdown(u);
	if(son[u][0]) print(son[u][0]);
	if(w[u]>1&&w[u]<n+2)printf("%d ",w[u]-1);
    if(son[u][1]) print(son[u][1]);
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n+2;++i)insert(i);
	while(m--)
	{
		l=read();r=read();
		work(l,r);
	}
	print(root);
	return 0;	
}

7.主席树

           主席树貌似是提高组考点来着orz...然而弱爆了的南瓜现在才会打板子呜呜呜

           目前了解到的主席树的作用一共就两个吧(学艺不精QAQ)

        1.区间k大值

                     考虑离散化,按权值从小到大进行插入操作,然后对于一个查询r到l的k大值,我们只需要将第r颗树减去第l颗树,就可以得到在原来每个区间中有多少个数,在树上跑一遍第k个数的位置,再把值输出来就好啦(主要是有一个离散化后的数组和原来的数组说起来会有点点绕orz,由于南瓜语言表达能力实在是太弱啦,就感性理解一下下吧qwq

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define maxn 200010<<5
#define debug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;

int n,m,end,cnt,pos,l,r,k;
int a[maxn],b[maxn],root[maxn],sum[maxn],son[maxn][2];

int read()
{
	int xx=0,kk=1;char ch=' ';
	while(!isdigit(ch)){ch=getchar();if(ch=='-')kk=-1;}
	while(isdigit(ch)){xx=xx*10+ch-'0';ch=getchar();}
	return kk*xx;
}

void build(int &num,int l,int r)
{
	num=++cnt;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(son[num][0],l,mid);
	build(son[num][1],mid+1,r);
}

int add(int pre,int l,int r)
{
	int now=++cnt;sum[now]=sum[pre]+1;
	son[now][0]=son[pre][0],son[now][1]=son[pre][1];
	if(l==r) return now;
	int mid=(l+r)>>1;
	if(pos<=mid) son[now][0]=add(son[now][0],l,mid);
	else son[now][1]=add(son[now][1],mid+1,r);
	return now;
}

int query(int x,int y,int l,int r,int k)
{
	if(l==r) return b[l];
	int mid=(l+r)>>1,del=sum[son[x][0]]-sum[son[y][0]];
	if(del>=k) return query(son[x][0],son[y][0],l,mid,k);
    return query(son[x][1],son[y][1],mid+1,r,k-del);
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)
	    b[i]=a[i]=read();
	sort(b+1,b+1+n);
	end=unique(b+1,b+1+n)-b-1;
	build(root[0],1,end);
	for(int i=1;i<=n;++i)
	{
		pos=lower_bound(b+1,b+end+1,a[i])-b;
		root[i]=add(root[i-1],1,end);
	}
	for(int i=1;i<=m;++i)
	{
		l=read(),r=read(),k=read();
		printf("%d\n",query(root[r],root[l-1],1,end,k));
	}
	return 0;
}

        2.求t时刻的值

                     这个很好理解啦,比没有前一个那么绕orz,就直接上代码啦

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define maxn 1000005<<5
#define debug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;

int n,m,val,cnt,tim,opt,pos;
int a[maxn],root[maxn],w[maxn],son[maxn][2];

int read()
{
	int xx=0,kk=1;char ch=' ';
	while(!isdigit(ch)){ch=getchar();if(ch=='-')kk=-1;}
	while(isdigit(ch)){xx=xx*10+ch-'0';ch=getchar();}
	return kk*xx;
}

void build(int &now,int l,int r)
{	
	now=++cnt;
	if(l==r){w[now]=a[l];return;} 
	int mid=(l+r)>>1;
	build(son[now][0],l,mid);
	build(son[now][1],mid+1,r);
}

void add(int &now,int pre,int l,int r,int pos,int val)
{
	
	now=++cnt;w[now]=w[pre];
	son[now][0]=son[pre][0],son[now][1]=son[pre][1];
	if(l==r){w[now]=val;return;}
	int mid=(l+r)>>1;
	if(mid>=pos) add(son[now][0],son[pre][0],l,mid,pos,val);
	else add(son[now][1],son[pre][1],mid+1,r,pos,val);
}

int query(int now,int l,int r,int pos)
{
	if(l==r) return w[now];
	int mid=(l+r)>>1;
	if(mid>=pos) return query(son[now][0],l,mid,pos);
	else return query(son[now][1],mid+1,r,pos);
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	build(root[0],1,n);
	for(int i=1;i<=m;++i) 
	{
	    tim=read(),opt=read(),pos=read();
		switch(opt)
		{
			case 1:{val=read();add(root[i],root[tim],1,n,pos,val);break;}
			case 2:{printf("%d\n",query(root[tim],1,n,pos));root[i]=root[tim];break;}
		}
	}
	return 0; 
}

        然后后排提示一下,主席树的空间非常炸,一般开到nlogn(一般是开2^5倍叭?!)就收手啦,小了会re,大了会mle,不仅如此,大常数选手南瓜还疯狂在tle的边缘试探qwq

                  

8.LCT

最后这个由于我太菜啦,所以连干什么的都不知道QAQ

果然还是太弱啦!还要加油哦qvq

To Be Continued...

猜你喜欢

转载自blog.csdn.net/qq_40942982/article/details/81088125