[早期][复习资料]数据结构总结

好久以前写的博客了,想了想还是搬过来吧。

学了好几天数据结构了,今天就来做个总结吧

一、并查集和堆

并查集

并查集,顾名思义,就是可并可查的集合

并查集,就是可以实现两个集合相并,查询两个元素是否在同一集合的一种数据结构

然后并查集的查找一般是这样的:

int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}

合并:

void merge(int x,int y){
	x=find(x);y=find(y);
	if(x==y)return;
   	fa[x]=y;
}

并查集的优化一般是优化合并(一般不必用启发式合并,除非来个可持久化),启发式合并可以记录元素个数或树高,每次合并时把个数少(树高小)的并到个数多(树高大)的里面去就可以了:

void merge(int x,int y){
	x=find(x);y=find(y);
	if(x==y)return;
	if(size[x]<size[y])fa[x]=y,size[y]+=size[x];
	else fa[y]=x,size[x]+=size[y];
	/*
	if(height[x]<height[y])fa[x]=y;
	else{
		fa[y]=x;
		if(height[x]==height[y])++height[x];
	}
	*/
}

然后除了普通的并查集,还有带权并查集(查找的时候有点变化),种类并查集(就是设虚点)等,都和普通并查集大同小异

并查集可以用来判环,判连通性等,并查集也有一些骚操作,比如离线染色等

堆(优先队列)是一种可以在\(log\)级别快速查询优先级最低的元素的数据结构,一般可以用来优化\(dp\),在“可反悔”的贪心中也可能会用到

堆有很多种,二叉堆、左偏树等都是常见的堆

二叉堆(应该是最容易写的,空间最小的)支持\(top()\)\(pop()\)\(push()\)操作

二叉堆(小根堆)的代码:

int a[maxn],num;
void push(int x){
	int now=++num;
	a[++now]=x;
	while(now>1&&a[now]<a[now/2])
		swap(a[now],a[now/2]),now/=2;
}
void pop(int x){
	int now=1;
	swap(a[now],a[num--]);
	while(now*2<=num){
		int nt=now;
		if(now*2<=num&&a[now*2]<a[nt])nt=now*2;
		if(now*2+1<=num&&a[now*2+1]<a[nt])nt=now*2+1;
		if(nt==now)break;
		swap(a[now],a[nt]);
		now=nt;
	}
}
int top(void){
	return a[1];
}

左偏树,顾名思义,就是一颗朝左偏的树

左偏树除了支持二叉堆的操作外,还支持一个\(merge()\)操作,就是将两个堆实行合并,二叉堆也可合并,但时间复杂度不可接受,而左偏树的复杂度则最坏也是\(log\)级别的

左偏树的实现靠的是一个\(dist\)数组,其保存的是任意一个节点到离它最近的叶子节点的距离,特别的,如果是是叶子结点,其\(dist\)就是\(0\)

左偏树除了满足堆性质,还满足左偏性质,即对于每个节点而言,都有\(dist[rson]<=dist[lson]\),因此有\(dist[now]=dist[rson]+1\),这样可以保证向右边递归不会超过\(log_2n\)

\(merge()\)的代码大概是这样的:

//小根堆 
int merge(int x,int y){
	//将两个左偏树进行合并,返回合并后的根 
	if(x==NULL)return y;
	if(y==NULL)return x;
	//如果一个节点为空,返回另一个节点 
	if(val[x]>val[y])swap(x,y);
	rs[x]=merge(rs[x],y);//将值小的作为根,右子树递归处理
	if(dist[rs[x]]>dist[ls[x]])swap(rs[x],ls[x]);
	dist[x]=dist[rs[x]]+1;//更新 
	return x; 
}

其它操作都是建立在\(merge()\)操作之上,比如\(push()\)就是把一个只含一个节点的左偏树和一棵左偏树合并

除了左偏树,还有其它的可并堆,如二项堆,斐波那契堆等

二项堆是由许多二项树组成的一座森林,各个操作的时间复杂度和左偏树相同,度数为\(0\)的二项树包含一个节点,度数为\(k\)的二项树的根节点有\(k\)个儿子,其度数分别为\(0,1,2,...,k-1\),两个度数相同的二项树合并时只需把根优先级大的作为另一棵树根的儿子就可以了,而二项堆的合并就是一直把度数相同的二项树合并,删除时可转为合并

不难发现,度数为\(k\)的二项树的节点个数刚好是\(2^k\),所以说二项树、二项堆和二进制有着很大关系,而二项堆合并时就像两个二进制数的相加,这很有启发意义

斐波那契堆删除时复杂度太高,一般不用

二、hash、KMP、扩展KMP、Trie以及AC自动机

hash

\(hash\)(散列)类似一种压缩映射,比如说八数码,广搜时要记录是否搜过,直接开数组空间会炸,就要用\(hash\)技术,把范围大的压缩到范围小的部分中去(也可用康托展开,但多一个\(log\)

对于一个数的映射,一般用取模(比如某个\(8\)位质数?),也可用一些稀奇古怪的方法(感觉就是乱搞)

既然有\(hash\),就避免不了\(hash\)冲突,有一些方法来避免,如开放选址法(这地被占了就换个地儿),再散列法(多\(hash\)几次总能找到地儿),链地址法(相同就住一间屋),建立一个公共溢出区(公共住宿?)等

还有其它的也要\(hash\),比如字符串等,字符串可以视作一个\(B\)进制的数,然后用\(hash\)数的方法去\(hash\)

同时,也可以用另外的方法...

//用unsigned防溢出变负数
// BKDR Hash Function
unsigned int BKDRHash(char *str){
	unsigned int seed=131;//31 131 1313 13131 131313 etc..
	unsigned int hash=0;
	while(*str){
		hash=hash*seed +(*str++);
	}
	return (hash&0x7FFFFFFF);
}
// AP Hash Function
unsigned int APHash(char *str){
	unsigned int hash=0;
	for(int i=0;*str;i++){
		if((i&1)==0)hash^=((hash<<7)^(*str++)^(hash >>3));
		else hash^=(~((hash<<11)^(*str++)^(hash>>5)));
	}
	return (hash&0x7FFFFFFF);
}

就是乱搞

KMP、扩展KMP

KMP是一种可以在线性时间内实现一个字符串和另一个字符串匹配的算法,它依赖于一个\(next\)数组,其中\(next[i]\)是前\(i\)个字符(或前\(i+1\)个字符)的最长公共前后缀(不包含前\(i\)个字符所有)的长度(或长度\(-1\)

由于数组坐标从\(0\)开始,这里的\(next[i]\)是前\(i+1\)个字符组成的串的最长公共前后缀(不包含所有)的长度\(-1\)

\(next\)数组:

int next[maxn],lenb;char b[maxn];
void getnext(void){
	next[0]=-1;
	for(int i=1;i<lenb;++i){
		int j=next[i-1];
		while(j>=0&&b[j+1]!=b[i])
			j=next[j];
		if(b[j+1]==b[i])next[i]=j+1;
		else next[i]=-1;
	}
}

匹配:

char a[maxn];int lena;
void solve(void){
	int i=0,j=0;
	while(i<lena){
		if(a[i]==b[j]){
			++i,++j;
			if(j==lenb){
				//匹配成功,匹配位置为a[i-j]--a[i-1]
				j=next[j-1]+1;
			}
		}
		else{
			if(j)j=next[j-1]+1;
			else ++i;
		}
	}
}

KMP有时会用在动态规划里面,作为AC自动机的简化版?

扩展KMP就是KMP的扩展,用于求出对于\(A\)的每一个后缀,求出其与\(B\)的最长公共前缀,即对于每个\(i\),求出使得\(A[i]...A[i+z-1]\)\(B[0]...B[z-1]\)匹配的最大的\(z\)值,这种算法也需要一个\(next\)数组,\(next[i]\)表示满足\(B[i]...B[i+z-1]\)\(B[0]...B[z-1]\)匹配的最大的\(z\)

\(next\)数组:

int next[maxn],lenb;char b[maxn];
void getnext(void){
	int pos=1,j=0;
	next[0]=lenb;
	while(j+1<lenb&&b[j]==b[j+1])++j;
	next[1]=j;
	for(int i=2;i<lenb;++i){
		if(i+next[i-pos]<pos+next[pos])
			next[i]=next[i-pos];
		else{
			j=pos+next[pos]-i;if(j<0)j=0;
			while(j+i<lenb&&b[j+i]==b[j])++j;
			next[i]=j;pos=i;
		}
	}
}

求解(和求\(next\)差不多):

char a[maxn];int lena,ex[maxn];
void getex(void){
	int pos=0,j=0;
	while(j<lenb&&j<lena&&a[j]==b[j])++j;
	ex[0]=j;
	for(int i=1;i<lena;++i){
		if(i+next[i-pos]<pos+ex[pos])
			ex[i]=next[i-pos];
		else{
			int j=pos+ex[pos]-i;if(j<0)j=0;
			while(j<lenb&&j+i<lena&&a[j+i]==b[j])++j;
			ex[i]=j;pos=i;
		}
	}
}

扩展KMP时间复杂度也是线性的

Trie

Trie应该没什么好说的,挺容易的

建立:

int son[maxn][26],num,ed[maxn];
void insert(char *s){
	int len=strlen(s),now=0; 
	for(int i=0;i<len;++i){
		int o=s[i]-'a';
		if(!son[now][o])son[now][o]=++num;
		now=son[now][o];
	}
	++ed[now];
}

它可以支持查找某个单词(字符串)是否在给定单词中

AC自动机

可以自动AC的鸡?

AC自动机=KMP+Trie

AC自动机就像是KMP的升级版,KMP只能支持一个字符串的匹配,而AC自动机可以支持多个字符串的匹配

和KMP有\(next\)一样,AC自动机有一个\(fail\)指针,用于指向若匹配失败时的去向

AC自动机的插入:

int son[maxn][26],ed[maxn],num,cnt;
void insert(char *s){
	int len=strlen(s),now=0;
	for(int i=0;i<len;++i){
		int o=s[i]-'a';
		if(!son[now][o])son[now][o]=++num;
		now=son[now][o];
	}
	ed[++cnt]=now;//第cnt个字符串的结尾为now 
}

\(fail\)数组:

int q[maxn],fail[maxn];
void prepare(void){
	int head=0,tail=0;
	for(int i=0;i<26;++i)
		if(son[0][i])
			q[tail++]=son[0][i];
	while(head<tail){
		int now=q[head++];
		for(int i=0;i<26;++i){
			if(son[now][i]){
				fail[son[now][i]]=son[fail[now]][i];
				q[tail++]=son[now][i];
			}
			else son[now][i]=son[fail[now]][i];
		}
	}
}

匹配:

int size[maxn];
void add(char *s){
	int len=strlen(s),now=0;
	for(int i=0;i<len;++i){
		int o=s[i]-'a';
		now=son[now][o];
		for(int t=now;t;t=fail[t])
			++size[t];
	}
}

查询第\(x\)个字符串出现次数:

int query(int x){
	return size[ed[x]];
}

可以看到,匹配时有一段是暴力跳\(fail\)指针的:

for(int t=now;t;t=fail[t])
	++size[t];

这是不必要的,有时这样做会T,仔细看\(fail\)数组,发现对于每个节点,它的\(fail\)值只会有一个(这不是废话),并且只会指向深度比它浅的节点,就像是每个点只有一个出度的有向无环图,其实就是一棵树,每次跳\(fail\)指针就是找到一个节点往上走的所有祖先并\(++size\),所以可以构建一棵\(fail\)树,跳\(fail\)指针的过程就是链修改,在树上差分一下,最后再全部累加,就可以保证时间复杂度

如果边匹配边询问呢?

只需再用点数据结构(树状数组,线段树,树剖,LCT等)进行链修改就可以了

AC自动机还有很多特殊的操作,这里就不讲了

AC自动机可以出在动规的题目中,状态一般是设\(f[i][sta]\)表示在AC自动机上走到\(i\)号节点状态为\(sta\)的方案数等,并且一般要用矩乘优化

三、树状数组、线段树、st表、RMQ和LCA

树状数组和线段树

树状数组和线段树都是很基础的数据结构,在这里直接上代码吧

树状数组:

int tr[maxn],n;
int lowbit(int x){
	return x&-x;
}
int add(int x,int v){
	while(x<=n){
		tr[x]+=v;
		x+=lowbit(x);
	}
}
int sum(int x){
	int ans=0;
	while(x){
		ans=tr[x];
		x-=lowbit(x);
	}
	return ans;
}

线段树:

int tr[maxn<<2],la[maxn<<2];
void reset(int x,int v,int l,int r){
	if(v)tr[x]+=v*(r-l+1),la[x]+=v;
}
void push_down(int x,int l,int r){
	int mid=l+r>>1;
	reset(x<<1,la[x],l,mid);
	reset(x<<1|1,la[x],mid+1,r);
	la[x]=0;
}
void push_up(int x){
	tr[x]=tr[x<<1]+tr[x<<1|1];
}
void add(int x,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		reset(x,v,l,r);return;
	}
	push_down(x,l,r);int mid=l+r>>1;
	if(L<=mid)add(x<<1,l,mid,L,R,v);
	if(R>mid)add(x<<1|1,mid+1,r,L,R,v);
	push_up(x);
}
int query(int x,int l,int r,int L,int R){
	if(L<=l&&r<=R)return tr[x];
	push_down(x);int mid=l+r>>1,ans=0;
	if(L<=mid)ans+=query(x<<1,l,mid,L,R);
	if(R>mid)ans+=query(x<<1|1,mid+1,r,L,R);
	return ans;
}

线段树的懒标记值得研究一下,它的懒标记可以有很特殊的操作,如标记永久化等

线段树常数太大?不妨试试zkw线段树重口味线段树?

线段树还有一些骚操作,可以实现看似暴力的AC

其实线段树最恐怖的是和Splay的结合(后面会讲到)

st表、RMQ和LCA

RMQ和LCA其实差不多,互相可以转化

st表,可以用来解决RMQ问题,实现\(O(nlog_2n)-O(1)\),st表就是倍增的应用,比如对于求区间最小值,可以设\(f[i][j]\)表示\(A[i]...A[i+2^j-1]\)的最小值,然后区间\([L,R]\)的最小值便是\(min(f[L][\lfloor log_2len\rfloor ],f[R-2^{\lfloor log_2len\rfloor }+1][\lfloor log_2len\rfloor ])(len=R-L+1)\)

\(log_2\)可以预处理,代码如下:

int n,st[maxn][20],log_2[maxn];
int lowbit(int x){
	return x&-x;
}
void prepare(void){
	log_2[0]=-1;
	for(int i=1;i<=n;++i){
		log_2[i]=i==lowbit(i)?log_2[i-1]+1:log_2[i-1];
	}
	for(int i=1;i<=log_2[n];++i){
		for(int j=1;j+(1<<i)-1<=n;++j){
			st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
		}
	}
}
int query(int L,int R){
	int len=log_2[R-L+1];
	return min(st[L][len],st[R-(1<<len)+1][len]);
}

ST表有一个缺陷,就是只支持静态,不支持修改,除非是在前后添数

RMQ问题除了ST表解法外,还可以转换成LCA问题,只需建立一棵笛卡尔树

笛卡尔树的中序遍历是原数组,它还满足堆性质,\(O(n)\)建立的方法如下:

int a[maxn],n,root,ls[maxn],rs[maxn],fa[maxn],q[maxn];
void prepare(void){
	int t=0;root=0;
	for(int i=1;i<=n;++i){
		while(t){
			int now=q[t];
			if(a[now]<=a[i]){
				if(rs[now]){
					ls[i]=rs[now];
					fa[rs[now]]=i;
				}
				rs[now]=i;
				fa[i]=now;
				break;
			}
			--t;
		}
		if(!t){
			ls[i]=root;
			fa[root]=i;
			root=i;
		}
		q[++t]=i;
	}
}

建立好笛卡尔树,区间的最小值就是它们在笛卡尔树上的LCA了

求LCA有许多方法,LCA也可以转为RMQ问题(LCA->RMQ->LCA->...),LCA也可以倍增求,也可以用Tarjan求

LCA一般是隐藏在题目中,并不会直接考,所以需要一双慧眼来发现

四、平衡树、树链剖分、LCT和点分治

平衡树

一般是用Splay或Treap来实现(如果有时间,可以手打红黑树

Treap就不讲了(才不是因为我不会呢),感觉会Splay应该也差不多了

Splay,伸展树,查询删除操作等时间复杂度均摊\(O(log_2n)\)(表示不会证明,知道这个结论差不多了),它可以实现普通平衡树的所有功能,同时也可以实现某些特殊功能

具体有哪些功能呢,比如先来个区间删除,再来个区间插入、区间翻转、区间旋转、区间移植、区间加、区间乘、区间取最大值、最小值、区间最大字段和、区间和、区间平方和、区间gcd......

当看到这些题时,我选择死亡

Splay做平衡树的板子:

struct Splay{
	#define Root e[0].son[0]
	struct node{
		int sum,recy,son[2],fa,v;
		//sum:元素数 recy:出现次数 v:节点值 
	}e[100005];
	int num,points;//num:节点数 points:元素数 
	void updata(int x){
		e[x].sum=e[e[x].son[0]].sum+e[e[x].son[1]].sum+e[x].recy;
	}
	int identify(int x){
		return e[e[x].fa].son[0]==x?0:1;
	}
	void connect(int f,int s,int t){
		e[f].son[t]=s,e[s].fa=f;
	}
	void rotate(int s){
		int o=identify(s),f=e[s].fa;
		connect(f,e[s].son[1^o],0^o);
		connect(e[f].fa,s,identify(f));
		connect(s,f,1^o);
		if(f)updata(f);
		if(s)updata(s);
	}
	void splay(int at,int to){
		to=e[to].fa;
		while(e[at].fa!=to){
			int up=e[at].fa;
			if(e[up].fa==to)
				rotate(at);
			else if(identify(at)==identify(up)){
				rotate(up);
				rotate(at);
			}
			else{
				rotate(at);
				rotate(at);
			}
		}
	}
	int crepoint(int v,int fa){
		++num;
		e[num].v=v;
		e[num].fa=fa;
		e[num].recy=e[num].sum=1;
		return num;
	}
	void destroy(int x){
		e[x].fa=e[x].recy=e[x].son[0]=e[x].son[1]=e[x].sum=e[x].v=0;
		if(x==num)--num;
	}
	int find(int v){
		int now=Root;
		while(1){
			if(e[now].v==v){
				splay(now,Root);
				return now;
			}
			int nt=v>e[now].v?1:0;
			if(!e[now].son[nt])return 0;
			now=e[now].son[nt];
		}
	}
	int build(int v){
		++points;
		if(points==1){
			num=0;
			return Root=crepoint(v,0);
		}
		else{
			int now=Root;
			while(1){
				++e[now].sum;
				if(e[now].v==v){
					++e[now].recy;return now;
				}
				int nt=e[now].v>v?0:1;
				if(!e[now].son[nt]){
					return e[now].son[nt]=crepoint(v,now);
				}
				now=e[now].son[nt];
			}
		}
	}
	void push(int v){
		int ad=build(v);
		splay(ad,Root);
	}
	void pop(int v){
		int dete=find(v);
		if(!dete)return;
		--points;
		if(e[dete].recy>1){
			--e[dete].recy;
			--e[dete].sum;
			return;
		}
		if(!e[dete].son[0]){
			connect(0,e[dete].son[1],0);
		}
		else{
			int lef=e[dete].son[0];
			while(e[lef].son[1])lef=e[lef].son[1];
			splay(lef,e[dete].son[0]);
			int rig=e[dete].son[1];
			connect(lef,rig,1);connect(0,lef,0);
			updata(lef);
		}
		destroy(dete);
	}
	int rank(int v){
		if(!Root)return 0;
		int ans=0,now=Root;
		while(1){
			if(v==e[now].v){
				ans+=e[e[now].son[0]].sum+1;
				splay(now,Root);
				return ans;
			}
			int nt=v>e[now].v?1:0;
			if(nt)ans+=e[now].sum-e[e[now].son[1]].sum;
			if(!e[now].son[nt])return 0;
			now=e[now].son[nt];
		}
	}
	int atrank(int x){
		if(x>points)return -0x7fffffff;
		int now=Root;
		while(1){
			if(x>e[e[now].son[0]].sum&&x<=e[e[now].son[0]].sum+e[now].recy)return e[now].v;
			int nt=x>e[e[now].son[0]].sum+e[now].recy?1:0;
			if(nt)x-=e[e[now].son[0]].sum+e[now].recy;
			if(!e[now].son[nt])return 0;
			now=e[now].son[nt];
		}
	}
	int lower(int v){
		int now=Root,ans=-0x7fffffff;
		while(now){
			if(e[now].v<v)
				ans=max(ans,e[now].v);
			int nt=v<=e[now].v?0:1;
			now=e[now].son[nt];
		}
		return ans;
	}
	int upper(int v){
		int now=Root,ans=0x7fffffff;
		while(now){
			if(e[now].v>v)
				ans=min(ans,e[now].v);
			int nt=v<e[now].v?0:1;
			now=e[now].son[nt];
		}
		return ans;
	}
	void printtree(void){
		for(int i=0;i<=num;++i){
			printf("i:%d e[i].v:%d e[i].recy:%d e[i].sum:%d e[i].fa:%d e[i].son[0]:%d e[i].son[1]:%d\n",i,e[i].v,e[i].recy,e[i].sum,e[i].fa,e[i].son[0],e[i].son[1]);
		}
	}
	#undef Root
}Tree;

Splay打得手累心累,为啥不用multiset

关于Splay维护乱七八糟的东西,也放份代码(洛谷P2710 数列):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename T>inline void read(T &x){
	x=0;int f(1);char c(getchar());
	for(;!isdigit(c);c=getchar())if(c=='-')f=-f;
	for(; isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c-'0');
	x*=f;
}
template<typename T>inline void write(T x){
	if(x<0)putchar('-'),x=-x;
	if(x/10)write(x/10),x%=10;
	putchar(x+'0');
}
const int maxn=500005;
struct Splay{
	#define root P[0].son[0]
	#define nw P[x]
	#define fnw P[P[x].p]
	#define ls P[P[x].son[0]]
	#define rs P[P[x].son[1]]
	#define NONE 1001
	int all,q[maxn],num;
	struct Node{
		int son[2],p,key;
		int size,sum[4];
		int cov,rev;
		Node(){
			son[0]=son[1]=p=key=size=sum[0]=rev=0;cov=NONE;sum[1]=sum[2]=sum[3]=-NONE;
		}
		inline void reset(void){
			son[0]=son[1]=p=key=size=sum[0]=rev=0;cov=NONE;sum[1]=sum[2]=sum[3]=-NONE;
		}
	}P[maxn];
	inline void push_up(int x){
		nw.size=ls.size+rs.size+1;
		nw.sum[0]=ls.sum[0]+rs.sum[0]+nw.key;
		nw.sum[1]=max(ls.sum[1],ls.sum[0]+nw.key+max(0,rs.sum[1]));
		nw.sum[2]=max(rs.sum[2],rs.sum[0]+nw.key+max(0,ls.sum[2]));
		nw.sum[3]=max(max(ls.sum[3],rs.sum[3]),max(0,ls.sum[2])+nw.key+max(0,rs.sum[1]));
	}
	inline void reset(int x,int rev,int cov){
		if(cov!=NONE){
			nw.rev=0;nw.cov=cov;nw.key=cov;nw.sum[0]=cov*nw.size;
			if(cov>0)nw.sum[1]=nw.sum[2]=nw.sum[3]=cov*nw.size;
			else nw.sum[1]=nw.sum[2]=nw.sum[3]=cov;
		}
		else if(rev&&nw.cov==NONE){
			nw.rev^=rev;
			swap(nw.son[0],nw.son[1]);
			swap(nw.sum[1],nw.sum[2]);
		}
	}
	inline void push_down(int x){
		if(nw.son[0])reset(nw.son[0],nw.rev,nw.cov);
		if(nw.son[1])reset(nw.son[1],nw.rev,nw.cov);
		nw.rev=0;nw.cov=NONE;
	}
	inline void connect(int f,int x,int t){
		nw.p=f;fnw.son[t]=x;
	}
	inline int identify(int x){
		return fnw.son[1]==x;
	}
	inline void rotate(int x){
		int f=P[x].p;
		push_down(f);
		push_down(x);
		int t=identify(x);
		connect(f,nw.son[!t],t);
		connect(fnw.p,x,identify(f));
		connect(x,f,!t);
		push_up(f);
		push_up(x);
	}
	inline void splay(int at,int to){
		to=P[to].p;
		while(P[at].p!=to){
			int up=P[at].p;
			if(P[up].p==to)rotate(at);
			else if(identify(at)==identify(to)){
				rotate(up);rotate(at);
			}
			else{
				rotate(at);rotate(at);
			}
		}
	}
	inline int build(int *a,int l,int r,int f){
		if(l>r)return 0;
		int x=q[--num],mid=l+r>>1;
		nw.key=a[mid];nw.p=f;
		nw.son[0]=build(a,l,mid-1,x);
		nw.son[1]=build(a,mid+1,r,x);
		push_up(x);return x;
	}
	inline int sear(int k){
		int x=root;
		while(1){
			push_down(x);
			if(k==ls.size+1)return x;
			int nt=k>ls.size+1?1:0;
			if(nt)k-=ls.size+1;
			x=nw.son[nt];
		}
	}
	inline void ins(int *a,int pos,int tot){
		int now=build(a,1,tot,0);
		if(root==0){
			root=now;
		}
		else if(pos==0){
			int x=sear(1);
			splay(x,root);
			connect(x,now,0);
			push_up(x);
		}
		else if(pos==all){
			int x=sear(all);
			splay(x,root);
			connect(x,now,1);
			push_up(x);
		}
		else{
			int l=sear(pos),r=sear(pos+1);
			splay(l,root);splay(r,P[l].son[1]);
			connect(r,now,0);
			push_up(r);push_up(l);
		}
		all+=tot;
	}
	inline void recycle(int x){
		if(P[x].son[0])recycle(P[x].son[0]);
		if(P[x].son[1])recycle(P[x].son[1]);
		P[x].reset();q[num++]=x;
	}
	inline void del(int pos,int tot){
		if(pos==1&&pos+tot-1==all){
			if(root)recycle(root);root=0;
		}
		else if(pos==1){
			int x=sear(pos+tot);
			splay(x,root);
			recycle(P[x].son[0]);
			nw.son[0]=0;
			push_up(x);
		}
		else if(pos+tot-1==all){
			int x=sear(pos-1);
			splay(x,root);
			recycle(P[x].son[1]);
			nw.son[1]=0;
			push_up(x);
		}
		else{
			int l=sear(pos-1),r=sear(pos+tot);
			splay(l,root);splay(r,P[l].son[1]);
			recycle(P[r].son[0]);
			P[r].son[0]=0;
			push_up(r);push_up(l);
		}
		all-=tot;
	}
	inline void change(int pos,int tot,int rev=0,int cov=NONE){
		if(pos==1&&pos+tot-1==all){
			if(root)reset(root,rev,cov);else return;
		}
		else if(pos==1){
			int x=sear(pos+tot);
			splay(x,root);
			reset(nw.son[0],rev,cov);
			push_up(x);
		}
		else if(pos+tot-1==all){
			int x=sear(pos-1);
			splay(x,root);
			reset(nw.son[1],rev,cov);
			push_up(x);
		}
		else{
			int l=sear(pos-1),r=sear(pos+tot);
			splay(l,root);splay(r,P[l].son[1]);
			reset(P[r].son[0],rev,cov);
			push_up(r);push_up(l);
		}
	}
	inline int get_sum(int pos,int tot){
		if(pos==1&&pos+tot-1==all)return P[root].sum[0];
		else if(pos==1){
			int x=sear(pos+tot);
			splay(x,root);
			return ls.sum[0];
		}
		else if(pos+tot-1==all){
			int x=sear(pos-1);
			splay(x,root);
			return rs.sum[0];
		}
		else{
			int l=sear(pos-1),r=sear(pos+tot);
			splay(l,root);splay(r,P[l].son[1]);
			return P[P[r].son[0]].sum[0];
		}
	}
	inline int get(int pos){
		int x=sear(pos);return nw.key;
	}
	inline int max_sum(int pos,int tot){
		if(pos==1&&pos+tot-1==all)return P[root].sum[3];
		else if(pos==1){
			int x=sear(pos+tot);
			splay(x,root);
			return ls.sum[3];
		}
		else if(pos+tot-1==all){
			int x=sear(pos-1);
			splay(x,root);
			return rs.sum[3];
		}
		else{
			int l=sear(pos-1),r=sear(pos+tot);
			splay(l,root);splay(r,P[l].son[1]);
			return P[P[r].son[0]].sum[3];
		}
	}
	void print(int x){
		push_down(x);
		if(P[x].son[0])print(P[x].son[0]);
		write(nw.key);putchar(' ');
		if(P[x].son[1])print(P[x].son[1]);
	}
	void print(void){
		if(root)print(root);putchar('\n');
	}
	#undef NONE
	#undef root
	#undef fnw
	#undef nw
	#undef ls
	#undef rs
}T;
int a[maxn];
int main(){
	for(int i=1;i<=500000;++i)
		T.q[T.num++]=i;
	int N,M;
	read(N),read(M);
	for(int i=1;i<=N;++i){
		read(a[i]);
	}
	T.ins(a,0,N);
	while(M--){
		char s[15];
		scanf("%s",s);
		if(s[0]=='I'){
			int pos,tot;
			read(pos);read(tot);
			for(int i=1;i<=tot;++i)
				read(a[i]);
			T.ins(a,pos,tot);
		}
		if(s[0]=='D'){
			int pos,tot;
			read(pos);read(tot);
			T.del(pos,tot);
		}
		if(s[0]=='R'){
			int pos,tot;
			read(pos);read(tot);
			T.change(pos,tot,1);
		}
		if(s[0]=='M'&&s[2]=='K'){
			int pos,tot,t;
			read(pos);read(tot);read(t);
			T.change(pos,tot,0,t);
		}
		if(s[0]=='G'&&s[3]=='-'){
			int pos,tot;
			read(pos);read(tot);
			write(T.get_sum(pos,tot));
			putchar('\n');
		}
		if(s[0]=='G'&&s[3]=='\0'){
			int pos;read(pos);
			write(T.get(pos));
			putchar('\n');
		}
		if(s[0]=='M'&&s[2]=='X'){
			int pos,tot;
			read(pos);read(tot);
			write(T.max_sum(pos,tot));
			putchar('\n');
		}
	}
	return 0;
}

299行,打得真的是心累手累

树链剖分

树链剖分,可以实现\(O(log_2^2n)\)在树上链查询,链修改,比LCT作用小点

板子:

struct Node{
	int sz,dp,bs,p;
	int df,tp;
}P[maxn];
void dfs1(int fa,int x,int dp){
	P[x].p=fa;P[x].sz=1;P[x].dp=dp;int msz=0;
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(v==fa)continue;
		dfs1(x,v,dp+1);
		P[x].sz+=P[v].sz;
		if(P[v].sz>msz){
			msz=P[v].sz;
			P[x].bs=v;
		}
	}
}
void dfs2(int fa,int x,int tp){
	P[x].tp=tp;P[x].df=++T.N;
	if(P[x].bs){
		dfs2(x,P[x].bs,tp);
	}
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(v==fa||v==P[x].bs)continue;
		dfs2(x,v,v);
	}
}
void add(int x,int y,int z){
	while(P[x].tp!=P[y].tp){
		if(P[P[x].tp].dp<P[P[y].tp].dp)
			swap(x,y);
		T.add(P[P[x].tp].df,P[x].df,z);
		x=P[P[x].tp].p;
	}
	if(P[x].dp>P[y].dp)swap(x,y);
	T.add(P[x].df,P[y].df,z);
}
int query(int x,int y){
	int ans=0;
	while(P[x].tp!=P[y].tp){
		if(P[P[x].tp].dp<P[P[y].tp].dp)
			swap(x,y);
		ans=mo(ans+T.query(P[P[x].tp].df,P[x].df));
		x=P[P[x].tp].p;
	}
	if(P[x].dp>P[y].dp)swap(x,y);
	ans=mo(ans+T.query(P[x].df,P[y].df));
	return ans;
}

树剖和LCT等如果在题目中,一般是要仔细观察题目的性质,找清楚题目要维护的东西

LCT

LCT(link-cut-tree)是一种可以动态加边,删边的数据结构

直接上板子吧(求链异或和):

struct LCT{
	struct Node{
		int son[2],p,key,sum,rev;
	}P[maxn];
	#define ls P[P[x].son[0]]
	#define rs P[P[x].son[1]]
	#define nw P[x]
	#define fw P[P[x].p]
	inline void push_up(int x){
		nw.sum=nw.key;
		if(nw.son[0])nw.sum^=ls.sum;
		if(nw.son[1])nw.sum^=rs.sum;
	}
	inline int nroot(int x){
		return fw.son[0]==x||fw.son[1]==x;
	}
	inline int identify(int x){
		return fw.son[1]==x;
	}
	inline void reset(int x,int rev){
		if(rev){
			nw.rev^=rev;
			swap(nw.son[0],nw.son[1]);
		}
	}
	inline void push_down(int x){
		if(nw.son[0])reset(nw.son[0],nw.rev);
		if(nw.son[1])reset(nw.son[1],nw.rev);
		nw.rev=0;
	}
	inline void connect(int f,int x,int t){
		nw.p=f;fw.son[t]=x;
	}
	inline void rotate(int x){
		int fx=nw.p;
		push_down(fx);
		push_down(x);
		int t=identify(x);
		if(nw.son[!t])connect(fx,nw.son[!t],t);
		else fw.son[t]=0;
		if(nroot(fx))connect(fw.p,x,identify(fx));
		else nw.p=fw.p;
		connect(x,fx,!t);
		push_up(fx);
		push_up(x);
	}
	int st[maxn];
	inline void splay(int at){
		int temp=at,t=0;st[t++]=temp;
		while(nroot(temp))st[t++]=temp=P[temp].p;
		while(t)push_down(st[--t]);
		while(nroot(at)){
			int up=P[at].p;
			if(!nroot(up))rotate(at);
			else if(identify(at)==identify(up)){
				rotate(up);rotate(at);
			}
			else{
				rotate(at);rotate(at);
			}
		}
	}
	inline void access(int x){
		for(int y=0;x;y=x,x=nw.p){
			splay(x);nw.son[1]=y;push_up(x);
		}
	}
	inline void makeroot(int x){
		access(x);splay(x);reset(x,1);
	}
	inline int findroot(int x){
		access(x);splay(x);
		while(nw.son[0])push_down(x),x=nw.son[0];
		splay(x);return x;
	}
	inline void split(int x,int y){
		makeroot(x);access(y);splay(y);
	}
	inline int link(int x,int y){
		makeroot(x);
		if(findroot(y)==x)
			return 0;
		nw.p=y;
		return 1;
	}
	inline int cut(int x,int y){
		makeroot(x);
		if(findroot(y)!=x||nw.son[1]!=y||P[y].son[0])
			return 0;
		nw.son[1]=P[y].p=0;
		push_up(x);
		return 1;
	}
	#undef ls
	#undef rs
	#undef nw
	#undef fw
}T;

LCT可以维护很多东西,稍作修改也可维护子树和,应该说是十分全能的(至于常数,有O2嘛

点分治

点分治就是对点进行分治,可以用来提高一些依赖于树高的解法的效率

点分治的模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename T>void read(T &x){
	x=0;int f(1);char c(getchar());
	for(;!isdigit(c);c=getchar())if(c=='-')f=-f;
	for(; isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c-'0');
	x*=f;
}
template<typename T>void write(T x){
	if(x<0)putchar('-'),x=-x;
	if(x/10)write(x/10),x%=10;
	putchar(x+'0');
}
const int maxn=10004,maxm=104,maxk=10000007;
struct Edge{
	int v,w,nt;
}edge[maxn<<1];
int hd[maxn],num;
void add_edge(int u,int v,int w){
	++num,edge[num].v=v,edge[num].w=w,edge[num].nt=hd[u],hd[u]=num;
}
int size[maxn],test[maxm],len[maxm],dp[maxn],vis[maxn],exist[maxn],cnte,judge[maxk];
void dfs(int x,int fa){
	exist[cnte++]=dp[x];
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(vis[v]||v==fa)continue;
		dp[v]=dp[x]+edge[i].w;dfs(v,x);
	}
}
void getsize(int x,int fa){
	size[x]=1;
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(vis[v]||v==fa)continue;
		getsize(v,x);
		size[x]+=size[v];
	}
}
int allsize;
int getrt(int x,int fa){
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(vis[v]||v==fa)continue;
		if(size[v]*2>allsize)return getrt(v,x);
	}
	return x;
}
int getroot(int x){
	getsize(x,0);allsize=size[x];
	return getrt(x,0);
}
int m,q[maxn];
void solve(int x){
	judge[0]=1;int cnt=0;
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(vis[v])continue;
		cnte=0;dp[v]=edge[i].w;dfs(v,x);
		for(int j=0;j<cnte;++j){
			for(int k=0;k<m;++k){
				if(len[k]>=exist[j]&&len[k]-exist[j]<maxk&&judge[len[k]-exist[j]]){
					test[k]=true;
				}
			}
		}
		for(int j=0;j<cnte;++j)if(exist[j]<maxk)judge[q[cnt++]=exist[j]]=true;
	}
	for(int i=0;i<cnt;++i)judge[q[i]]=0;
}
void divide(int x){
	solve(x);vis[x]=1;
	for(int i=hd[x];i;i=edge[i].nt){
		int v=edge[i].v;
		if(vis[v])continue;
		int root=getroot(v);
		divide(root);
	}
}
int main(){
	int n;read(n);read(m);
	for(int i=1,u,v,w;i<n;++i){
		read(u);read(v);read(w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	for(int i=0;i<m;++i)read(len[i]);
	int t=getroot(1);
	divide(t);
	for(int i=0;i<m;++i){
		if(test[i])puts("AYE");
		else puts("NAY");
	}
	return 0;
}

点分树还没学懂,学懂再来总结吧

posted @ 2021-01-04 21:07  xiaolilsq  阅读(106)  评论(0编辑  收藏  举报