算法笔记——数据结构

  • 栈的定义:
    栈是一种先进先出的数据结构。建立如下:
#include<stack>
stack<int>stk;

同时也可以用数组模拟栈。

  • 栈的操作

1.进栈:

stk.push(x);

2.出栈(弹出栈顶元素):

stk.pop();

3.检查栈是否非空(空返回0,非空返回1):

stk.empty();

4.输出栈顶元素:

stk.top();

5.输出栈中元素个数:

stk.size();
  • 单调栈
    构造一种栈,使里面的元素单增或单减。
    代码(最普通的单调栈):
#include<cstdio>
#define maxn 100005
using namespace std;

int n;
int top,a[maxn],stk[maxn];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;++i)
	{
		while(top&&a[i]<stk[top])top--;
		stk[++top]=a[i];
	}
	for(int i=1;i<=top;++i)
	printf("%d ",stk[i]);
	return 0;
}

队列

  • 定义
    有头有尾。在尾删除,在头插入。建立如下:
#include<queue>
queue<int>que;
  • 队列的操作
    1.插入:
queue.push(x);

2.删除:

queue.pop();

3.访问队首元素:

queue.front();

4.访问队尾元素:

queue.back();

5.判断队列是否为空:

queue.empty();

6.询问队列的元素个数:

queue.size();
#include<bits/stdc++.h>
#define M 1000005
using namespace std;
int n,k,a[M],q[M],f[M],f_mx[M];//q数组存下标
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    if(k==1)
    {
        for(int i=1;i<=n;i++)
        printf("%d ",a[i]);
        printf("\n");
        for(int i=1;i<=n;i++)
        printf("%d ",a[i]);
        return 0;
    }
    int h=1,t=0;//定义头,尾指针
    for(int i=1;i<=n;i++)
    {
        while(h<=t&&i-q[h]>=k)h++;//判断队首有没有超过区间范围
    	if(i>=k)f[i-k+1]=min(a[q[h]],a[i]);//求解
    	while(h<=t&&a[i]<=a[q[t]])t--;//判断队尾是否大于队首
    	q[++t]=i;
    }//求最小值
    h=1,t=0;
    for(int i=1;i<=n;i++)
    {
        while(h<=t&&i-q[h]>=k)h++;//判断队首有没有超过区间范围
    	if(i>=k)f_mx[i-k+1]=max(a[q[h]],a[i]);//求解
    	while(h<=t&&a[i]>=a[q[t]])t--;//判断队尾是否大于队首
    	q[++t]=i;
    }
    for(int i=1;i<=n-k+1;i++)
    printf("%d ",f[i]);
    printf("\n");
    for(int i=1;i<=n-k+1;i++)
    printf("%d ",f_mx[i]);
    return 0;
}

  • 定义:一种特殊的二叉树。对顶元素一定是最小的或最大的。

  • 堆的种类:
    1.小根堆:堆顶元素最小。
    2.大根堆:堆顶元素最大。
    3.优先队列;
    4.随机堆;
    5.斜堆;
    6.左偏树;

  • 堆的实现:
    1.建立(貌似是优先队列):

#include<queue>
priority_queue<int,vector<int>,greater<int> >que;

2.插入:

que.push(x);

3.删除堆顶元素:

que.pop();

4.输出堆顶元素:

que.top();
  • 代码:
    1.普通堆:
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;

priority_queue<int,vector<int>,greater<int> >que;
int n;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
		if(x==1)
		{
			int y;
			scanf("%d",&y);
			que.push(y);
		}
		if(x==2)
		printf("%d\n",que.top());
		if(x==3)
		que.pop();
	}
	return 0;
}

2.左偏树:

#include<cstdio>
#include<algorithm>
#define maxn 150005
#define lid tr[x].ch[0]
#define rid tr[x].ch[1]
using namespace std;

struct node
{
	int val,dis,fa,ch[2];
}tr[maxn];//定义堆数组
int n,m;

int getfa(int x)
{
	if(tr[x].fa!=x)return tr[x].fa=getfa(tr[x].fa); 
	return x;
}//找根(路径压缩)

int merge(int x,int y)
{
	if(x==0||y==0)
	return x+y;
	if(tr[x].val>tr[y].val||(tr[x].val==tr[y].val&&x>y))
	swap(x,y);
	rid=merge(rid,y);
	if(tr[lid].dis<tr[rid].dis)
	swap(lid,rid);
	tr[lid].fa=tr[rid].fa=tr[x].fa=x;
	tr[x].dis=tr[rid].dis+1;
	return x;
}//左偏树的合并

void pop(int x)
{
	tr[x].val=-1;
	tr[lid].fa=lid;
	tr[rid].fa=rid;
	tr[x].fa=merge(lid,rid);
}

int main()
{
	scanf("%d%d",&n,&m);
	tr[0].dis=-1;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&tr[i].val);
		tr[i].fa=i;
	}
	for(int i=1;i<=m;++i)
	{
		int op;
		scanf("%d",&op);
		if(op==1)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			if(tr[x].val==-1||tr[y].val==-1)
			continue;
			if(x==y)
			continue;
			x=getfa(x);
			y=getfa(y);
			if(x!=y)
			tr[x].fa=tr[y].fa=merge(x,y);
		}
		else if(op==2)
		{
			int x;
			scanf("%d",&x);
			if(tr[x].val==-1)
			printf("-1\n");
			else
			{
				int y=getfa(x);
				printf("%d\n",tr[y].val);
				pop(y);
			}
		}
	}
	return 0;
}

ST表

  • 作用
    O ( 1 ) O(1) O(1)的复杂度求区间内最大值(RMQ问题)。

  • 思想
    倍增+动态规划。

  • 缺点
    不支持修改。

  • 应用
    结合其他算法,减少时间复杂度。

  • 代码

#include<cstdio>
#define maxn 1000005
using namespace std;

int n,m;
int a[maxn],st[maxn][4],log[maxn];

int max(int x,int y)
{
	return x>y?x:y;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	scanf("%d",&a[i]);
	log[0]=-1;
	for(int i=1;i<=n;++i)
	{
		st[i][0]=a[i];
		log[i]=log[i>>1]+1;
	}//预处理
	for(int j=1;j<=20;++j)
	{
		for(int i=1;i+(1<<j)-1<=n;++i)
		st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
	for(int i=1;i<=m;++i)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		int s=log[r-l+1];
		printf("%d\n",max(st[l][s],st[r-(1<<s)+1][s]));
	}
	return 0;
}

树状数组

  • 作用
    快速求前缀和、区间和。

  • 操作
    支持修改 O ( log ⁡ n ) O(\log n) O(logn),求和 O ( log ⁡ n ) O(\log n) O(logn)

  • 缺点
    普通的树状数组不支持区间修改。

  • 代码

1.求lowbit

int lowbit(int n)
{
	return n&(-n);
}//求lowbit

2.修改操作

void add(int x,int y)
{
	while(x<=n)
	{
		c[x]+=y;
		x+=lowbit(x);
	}
}//修改树状数组

3.查询操作

int query(int x)
{
	int sum=0;
	while(x)
	{
		sum+=c[x];
		x-=lowbit(x);
	}
	return sum;
}//输出前缀和

完整代码:

#include<bits/stdc++.h>
using namespace std;
int s[500005],c[500005],tot[500005],t,m,n;
int lowbit(int n)
{
	return n&(-n);
}//求lowbit
void add(int x,int y)
{
	while(x<=n)
	{
		c[x]+=y;
		x+=lowbit(x);
	}
}//修改树状数组
int query(int x)
{
	int sum=0;
	while(x)
	{
		sum+=c[x];
		x-=lowbit(x);
	}
	return sum;
}//输出前缀和
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		add(i,s[i]);
	}
	for(int i=1;i<=m;i++)
	{
		int a,x,y;
		scanf("%d%d%d",&a,&x,&y);
		if(a==1)
		add(x,y);
		if(a==2)
		tot[++t]=query(y)-query(x-1);	
	}
	for(int i=1;i<=t;i++)
	printf("%d\n",tot[i]);
	return 0;
}

支持区间修改的树状数组:差分树状数组
只需要小小的修改:
1.读入数据时:

for(int i=1;i<=n;i++)
scanf("%d",&s1[i]);
for(int i=1;i<=n;i++)
{
	s2[i]=s1[i]-s1[i-1];
	add(i,s2[i]);
}//建立差分数组

2.区间修改时:

int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,z);
add(y+1,-z);

ok!


线段树

  • 定义
    一种数据结构,一个节点存储一个区间的信息。

  • 思想
    二分。

  • 应用
    1.处理区间或单点信息(区间加,区间乘,区间赋值);
    2.作为一种辅助算法减少时间复杂度。

  • 实现

1.普通线段树
a.建树

void build(ll k,ll l,ll r)
{
	tr[k].l=l;
	tr[k].r=r;
	tr[k].flag1=0;
	tr[k].flag2=1;
	if(l==r)
	{
		tr[k].sum=a[l];
		return;
	}
	ll mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	update(k);
}//建树

b.区间加

void modify1(ll k,ll l,ll r,ll del)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	{
		tr[k].sum+=del*(tr[k].r-tr[k].l+1);
		tr[k].flag1+=del;
		return;
	}
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	modify1(lid,l,r,del);
	if(r>mid)
	modify1(rid,l,r,del);
	update(k);
}//区间加

c.区间乘

void modify2(ll k,ll l,ll r,ll del)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	{
		tr[k].sum*=del;
		tr[k].flag1*del;
		tr[k].flag2*=del;
		return;
	}
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	modify2(lid,l,r,del);
	if(r>mid)
	modify2(rid,l,r,del);
	update(k);
}//区间乘

d.标记下放(难点)

void pushdown(ll k)
{
	ll l=tr[k].l,r=tr[k].r;
	ll mid=(l+r)>>1;
	tr[lid].flag2*=tr[k].flag2;
	tr[lid].flag1*=tr[k].flag2;
	tr[lid].sum*=tr[k].flag2;
	tr[rid].flag2*=tr[k].flag2;
	tr[rid].flag1*=tr[k].flag2;
	tr[rid].sum*=tr[k].flag2;
	tr[k].flag2=1;
	tr[lid].flag1+=tr[k].flag1;
	tr[rid].flag1+=tr[k].flag1;
	tr[lid].sum+=(mid-l+1)*tr[k].flag1;
	tr[rid].sum+=(r-mid)*tr[k].flag1;
	tr[k].flag1=0;
}

e.区间和查询

ll query(ll k,ll l,ll r)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	return tr[k].sum;
	ll ret=0;
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	ret+=query(lid,l,r);
	if(r>mid)
	ret+=query(rid,l,r);
	return ret;
}//区间求和

f.所谓的上传(update)

void update(ll k)
{
	tr[k].sum=tr[lid].sum+tr[rid].sum;
}

完整代码:

#include<cstdio>
#define ll long long
#define maxn 1000005
#define lid (k<<1)
#define rid (k<<1|1)
using namespace std;

struct node
{
	ll l,r;
	ll flag1,flag2;
	ll sum;
}tr[maxn*4];
ll n,m,mod;
ll a[maxn];

void update(ll k)
{
	tr[k].sum=tr[lid].sum+tr[rid].sum;
}

void pushdown(ll k)
{
	ll l=tr[k].l,r=tr[k].r;
	ll mid=(l+r)>>1;
	tr[lid].flag2*=tr[k].flag2;
	tr[lid].flag1*=tr[k].flag2;
	tr[lid].sum*=tr[k].flag2;
	tr[rid].flag2*=tr[k].flag2;
	tr[rid].flag1*=tr[k].flag2;
	tr[rid].sum*=tr[k].flag2;
	tr[k].flag2=1;
	tr[lid].flag1+=tr[k].flag1;
	tr[rid].flag1+=tr[k].flag1;
	tr[lid].sum+=(mid-l+1)*tr[k].flag1;
	tr[rid].sum+=(r-mid)*tr[k].flag1;
	tr[k].flag1=0;
}

void build(ll k,ll l,ll r)
{
	tr[k].l=l;
	tr[k].r=r;
	tr[k].flag1=0;
	tr[k].flag2=1;
	if(l==r)
	{
		tr[k].sum=a[l];
		return;
	}
	ll mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	update(k);
}//建树

void modify1(ll k,ll l,ll r,ll del)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	{
		tr[k].sum+=del*(tr[k].r-tr[k].l+1);
		tr[k].flag1+=del;
		return;
	}
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	modify1(lid,l,r,del);
	if(r>mid)
	modify1(rid,l,r,del);
	update(k);
}//区间加

void modify2(ll k,ll l,ll r,ll del)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	{
		tr[k].sum*=del;
		tr[k].flag1*del;
		tr[k].flag2*=del;
		return;
	}
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	modify2(lid,l,r,del);
	if(r>mid)
	modify2(rid,l,r,del);
	update(k);
}//区间乘

ll query(ll k,ll l,ll r)
{
	if(tr[k].l>=l&&tr[k].r<=r)
	return tr[k].sum;
	ll ret=0;
	ll mid=(tr[k].l+tr[k].r)>>1;
	if(tr[k].flag2!=1||tr[k].flag1)
	pushdown(k);
	if(l<=mid)
	ret+=query(lid,l,r);
	if(r>mid)
	ret+=query(rid,l,r);
	return ret;
}//区间求和

ll main()
{
	scanf("%d%d",&n,&m);
	for(ll i=1;i<=n;++i)
	scanf("%d",&a[i]);
	build(1,1,n);
	for(ll i=1;i<=m;++i)
	{
		ll opt;
		ll x,y,k;
		scanf("%d",&opt);
		switch (opt)
		{
			case 1:
				scanf("%d%d%d",&x,&y,&k);
				modify2(1,x,y,k);
				break;
			case 2:
				scanf("%d%d%d",&x,&y,&k);
				modify1(1,x,y,k);
				break;
			case 3:
				scanf("%d%d",&x,&y);
				printf("%d\n",query(1,x,y));
				break;
		}
	}
	return 0;
}

模板题:
a.【模板】线段树 1;
b.【模板】线段树 2

2.可持久化线段树(主席树)
与普通的线段树不同的是,主席树可以询问历史版本,因而在代码上(主要是建树)略有不同。

#include<cstdio>
#define maxn 1000005
using namespace std;
int m,n,ndnum,tot,lc[maxn*24],rc[maxn*24],root[maxn];
int val[maxn],sum[maxn*24],answer[maxn];
void copynode(int a,int b)
{
	lc[a]=lc[b],rc[a]=rc[b],sum[a],sum[b];
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		sum[x]=val[l];
		return;
	}
	int mid=(l+r)>>1;
	build(lc[x]=++ndnum,l,mid);
	build(rc[x]=++ndnum,mid+1,r);
	sum[x]=sum[lc[x]]+sum[rc[x]];
}
int modify(int rt,int l,int r,int pos,int del)
{
	int t=++ndnum;
	copynode(t,rt);
	if(l==r)
	{
		sum[t]=del;
		return t;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)
	lc[t]=modify(lc[t],l,mid,pos,del);
	else
	rc[t]=modify(rc[t],mid+1,r,pos,del);
	sum[t]=sum[lc[t]]+sum[rc[t]];
	return t;
}
int query(int x,int l,int r,int pos)
{
	if(l==r)
	return sum[x];
	int mid=(l+r)>>1;
	if(pos<=mid)return query(lc[x],l,mid,pos);
	else return query(rc[x],mid+1,r,pos);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	scanf("%d",&val[i]);
	build(root[0]=++ndnum,1,n);
	for(int i=1;i<=m;i++)
	{
		int v,stp;
		scanf("%d%d",&v,&stp);
		if(stp==1)
		{
			int x;
			int y;
			scanf("%d%d",&x,&y);
			root[i]=modify(root[v],1,n,x,y);
		}
		if(stp==2)
		{
			int x;
			scanf("%d",&x);
			answer[++tot]=query(root[v],1,n,x);
			root[i]=root[v];
		}
	}
	for(int i=1;i<=tot;i++)
	printf("%d\n",answer[i]);
	return 0;
}

模板题:
a.【模板】可持久化线段树 1(主席树)
b.【模板】可持久化数组(可持久化线段树/平衡树)

#include<cstdio>
#define maxn 100005
#define maxm 300005
using namespace std;

int n,m,q,ndnum;
int lc[maxn*24],rc[maxn*24],sum[maxn*24],num[maxn*24];
int root[maxn],fa[maxn];
char op[15];

void update(int k)
{
	sum[k]=sum[lc[k]]+sum[rc[k]];
}

int build(int k,int l,int r,int del,int nu)
{
	if(!k)k=++ndnum;
	if(l==r)
	{
		num[k]=nu;
		sum[k]++;
		return k;
	}
	int mid=(l+r)>>1;
	if(del<=mid)
	lc[k]=build(lc[k],l,mid,del,nu);
	else
	rc[k]=build(rc[k],mid+1,r,del,nu);
	update(k);
	return k;
}//建立值域线段树

int merge(int x,int y,int l,int r)
{
	if(!x)return y;
	if(!y)return x;
	if(x==y)
	{
		if(num[y])
		{
			num[x]=num[y];
			sum[x]+=sum[y];
		}
		return x;
	}
	int mid=(l+r)>>1;
	lc[x]=merge(lc[x],lc[y],l,mid);
	rc[x]=merge(rc[x],rc[y],mid+1,r);//合并子树
	update(x);
	return x;
}

int find(int x)
{
	if(x!=fa[x])
	return fa[x]=find(fa[x]);
	return fa[x];
}

void unionn(int x,int y)
{
	x=find(x);
	y=find(y);
	if(x==y)return;
	fa[y]=x;//并查集合并
	root[x]=merge(root[x],root[y],1,n);//线段树合并
}

int query(int rt,int del,int l,int r)
{
	if(sum[rt]<del||!rt)return 0;
	if(l==r)return num[rt];
	int mid=(l+r)>>1,ret=0;
	if(del<=sum[lc[rt]])
	ret=query(lc[rt],del,l,mid);
	else
	ret=query(rc[rt],del-sum[lc[rt]],mid+1,r);
	return ret;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
		fa[i]=i;
		root[i]=build(root[i],1,n,x,i);
	}//建树
	for(int i=1;i<=m;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		unionn(x,y);//建桥(合并并查集和线段树)
	}
	scanf("%d",&q);
	for(int i=1;i<=q;++i)
	{
		int x,y;
		scanf("%s",op);
		if(op[0]=='B')
		{
			scanf("%d%d",&x,&y);
			unionn(x,y);//建桥
		}
		else
		{
			scanf("%d%d",&x,&y);
			x=find(x);
			int ans=query(root[x],y,1,n);
			if(!ans)printf("-1\n");
			else printf("%d\n",ans);
		}
	}
	return 0;
}

习题:
1.The Child and Sequence;
2.[SHOI2015]脑洞治疗仪
3.CPU监控;
4.楼房重建


树链剖分

  • 概念
    线段树+图论。

  • 几个定义
    1.dfs序:从根节点开始,先遍历左儿子,再遍历右儿子,所形成的序列叫dfs序。
    2.重儿子:左儿子和右儿子中子树较大的那个儿子。
    3.链:树上两点之间的路径。

  • 代码:

1.建树:

void add(int x,int y)
{
	tot++;
	tr[tot].v=y;
	tr[tot].next=head[x];
	head[x]=tot;
}

2.找重儿子:

void dfs1(int x)
{
	size[x]=1;
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v;
		if(y==fa[x])continue;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs1(y);
		size[x]+=size[y];
		if(size[y]>size[son[x]])
		son[x]=y;//找重儿子
	}
}//确立父节点、深度,找重孩子

3.求dfs序及当前点走重儿子能走到的最浅的点:

void dfs2(int x,int tp)
{
	in[x]=++idc;//dfs序 
	seq[idc]=x;
	top[x]=tp;
	if(son[x])dfs2(son[x],tp);
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v;
		if(y==fa[x]||y==son[x])continue;
		dfs2(y,y);
	}
	out[x]=idc;
}//寻找当前点走重儿子能走到的最浅的点

4.建立线段树(注意根据的是dfs序建立):

void build(int k,int l,int r)
{
	pool[k].l=l;
	pool[k].r=r;
	if(l==r)
	{
		pool[k].sum=val[seq[l]];
		pool[k].flag=0;
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pool[k].sum=pool[lid].sum+pool[rid].sum;
	pool[k].flag=0;
}//建树

5.区间修改(一毛一样)

void modify(int k,int l,int r,int del)
{
	if(pool[k].l>=l&&pool[k].r<=r)
	{
		pool[k].sum+=(pool[k].r-pool[k].l+1)*del;
		pool[k].flag+=del;
		return;
	}
	int mid=(pool[k].l+pool[k].r)>>1;
	pushdown(k);
	if(l<=mid)
	modify(lid,l,r,del);
	if(r>mid)
	modify(rid,l,r,del);
	update(k);
}//区间修改

6.区间查询(一毛一样)

int query(int k,int l,int r)
{
	if(pool[k].l>=l&&pool[k].r<=r)
	return pool[k].sum;
	int mid=(pool[k].l+pool[k].r)>>1,ret=0;
	pushdown(k);
	if(l<=mid)
	ret+=query(lid,l,r);
	if(r>mid)
	ret+=query(rid,l,r);
	update(k);
	return ret;
}//区间查询

完整代码:

#include<cstdio>
#include<algorithm>
#define ll long long
#define maxn 100005
#define lid (k<<1)
#define rid (k<<1|1)
using namespace std;

struct node
{
	int v,next;
}tr[maxn<<1];
struct tree
{
	int l,r;
	int sum,flag;
}pool[maxn*4];
int n,m,r;
int tot,head[maxn];
int idc,size[maxn],dep[maxn],fa[maxn],son[maxn],top[maxn],in[maxn],out[maxn],seq[maxn];//树链剖分数组
ll val[maxn];

void pushdown(int k)
{
	int l,r,mid,flag;
	l=pool[k].l;
	r=pool[k].r;
	flag=pool[k].flag;
	mid=(l+r)>>1;
	if(!flag)return;
	pool[lid].flag+=flag;
	pool[lid].sum+=(mid-l+1)*flag;
	pool[rid].flag+=flag;
	pool[rid].sum+=(r-mid)*flag;
	pool[k].flag=0;
}//下放标记

void update(int k)
{
	pool[k].sum=pool[lid].sum+pool[rid].sum;
}

void add(int x,int y)
{
	tot++;
	tr[tot].v=y;
	tr[tot].next=head[x];
	head[x]=tot;
}

void dfs1(int x)
{
	size[x]=1;
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v;
		if(y==fa[x])continue;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs1(y);
		size[x]+=size[y];
		if(size[y]>size[son[x]])
		son[x]=y;//找重儿子
	}
}//确立父节点、深度,找重孩子

void dfs2(int x,int tp)
{
	in[x]=++idc;//dfs序 
	seq[idc]=x;
	top[x]=tp;
	if(son[x])dfs2(son[x],tp);
	for(int t=head[x];t;t=tr[t].next)
	{
		int y=tr[t].v;
		if(y==fa[x]||y==son[x])continue;
		dfs2(y,y);
	}
	out[x]=idc;
}//寻找当前点走重儿子能走到的最浅的点

void build(int k,int l,int r)
{
	pool[k].l=l;
	pool[k].r=r;
	if(l==r)
	{
		pool[k].sum=val[seq[l]];
		pool[k].flag=0;
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pool[k].sum=pool[lid].sum+pool[rid].sum;
	pool[k].flag=0;
}//建树

void modify(int k,int l,int r,int del)
{
	if(pool[k].l>=l&&pool[k].r<=r)
	{
		pool[k].sum+=(pool[k].r-pool[k].l+1)*del;
		pool[k].flag+=del;
		return;
	}
	int mid=(pool[k].l+pool[k].r)>>1;
	pushdown(k);
	if(l<=mid)
	modify(lid,l,r,del);
	if(r>mid)
	modify(rid,l,r,del);
	update(k);
}//区间修改

int query(int k,int l,int r)
{
	if(pool[k].l>=l&&pool[k].r<=r)
	return pool[k].sum;
	int mid=(pool[k].l+pool[k].r)>>1,ret=0;
	pushdown(k);
	if(l<=mid)
	ret+=query(lid,l,r);
	if(r>mid)
	ret+=query(rid,l,r);
	update(k);
	return ret;
}//区间查询

void add1(int x,int y,int z)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		modify(1,in[top[x]],in[x],z);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	modify(1,in[y],in[x],z);
}//树从x到y结点最短路径上所有节点的值都加上z

int add2(int x,int y)
{
	int ret=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		ret+=query(1,in[top[x]],in[x]);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	ret+=query(1,in[y],in[x]);
	return ret;
}//从x到y结点最短路径上所有节点的值之和

void add3(int x,int z)
{
	modify(1,in[x],out[x],z);
}//x为根节点的子树内所有节点值都加上z

int add4(int x)
{
	return query(1,in[x],out[x]);
}//以x为根节点的子树内所有节点值之和

int main()
{
	scanf("%d%d%d",&n,&m,&r);
	for(int i=1;i<=n;++i)
	scanf("%d",&val[i]);
	for(int i=1;i<n;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dep[r]=1;
	fa[r]=0;
	dfs1(r);
	dfs2(r,r);
	build(1,1,idc);
	for(int i=1;i<=m;++i)
	{
		int op,x,y,z;
		scanf("%d",&op);
		switch (op)
		{
			case 1:
				scanf("%d%d%d",&x,&y,&z);
				add1(x,y,z);
				break;
			case 2:
				scanf("%d%d",&x,&y);
				printf("%d\n",add2(x,y));
				break;
			case 3:
				scanf("%d%d",&x,&z);
				add3(x,z);
				break;
			case 4:
				scanf("%d",&x);
				printf("%d\n",add4(x));
				break;
		}
	}
	return 0;
}

平衡树

  • 定义
    平衡树是一种高级数据结构,满足二叉查找树的性质(即左儿子的权值小于父节点,右儿子的权值大于父节点)。
    注:这里的权值有两种定义,一是以节点的大小为权值,二是以节点的编号为权值。

  • 代码

1.Treap平衡树
特点:操作简单,码量小。
缺点:有限制,不能做区间反转等。

a.建树:

struct treap
{
	int l,r;
	int val,dat;//val是实际权值,dap是随机权值
	int size,cnt;//size是子树大小,cnt是元素个数
}tr[maxn];

b.更新:

void update(int p)
{
	tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
}//更新子树大小

c.建立新节点:

int New(int val)
{
	tot++;
	tr[tot].val=val;
	tr[tot].dat=rand();//赋予随机值
	tr[tot].cnt=tr[tot].size=1;
	return tot;
}//建立新节点

d.右旋(zig):

void zig(int &p)
{
	int q=tr[p].l;
	tr[p].l=tr[q].r;
	tr[q].r=p;
	p=q;
	update(tr[p].r);
	update(p);
}//右旋操作

e.左旋(zag):

void zag(int &p)
{
	int q=tr[p].r;
	tr[p].r=tr[q].l;
	tr[q].l=p;
	p=q;
	update(tr[p].l);
	update(p);
}//左旋操作

f.建立初始节点(防止越界):

void build()
{
	New(-inf),New(inf);
	root=1;
	tr[1].r=2;
	update(root);
}//初始化操作

g.插入节点:

void insert(int &p,int val)
{
	if(p==0)
	{
		p=New(val);
		return;
	}
	if(val==tr[p].val)
	{
		tr[p].cnt++;
		update(p);
		return;
	}
	if(val<tr[p].val)
	{
		insert(tr[p].l,val);//插入左子树
		if(tr[p].dat<tr[tr[p].l].dat)zig(p);//不满足堆的性质,右旋
	}
	else
	{
		insert(tr[p].r,val);//插入右子树
		if(tr[p].dat<tr[tr[p].r].dat)zag(p);//不满足堆的性质,左旋
	}
	update(p);
}//插入节点

h.删除节点:

void remove(int &p,int val)
{
	if(p==0)return;
	if(val==tr[p].val)
	{
		if(tr[p].cnt>1)
		{
			tr[p].cnt--;
			update(p);
			return;
		}//有重复
		if(tr[p].l||tr[p].r)
		{
			if(tr[p].r==0||tr[tr[p].l].dat>tr[tr[p].r].dat)
			{
				zig(p);
				remove(tr[p].r,val);
			}//空右节点就把当前节点移到右节点
			else
			{
				zag(p);
				remove(tr[p].l,val);
			}//空左节点就把当前节点移到左节点
			update(p);
		}//不是叶子节点,向下旋转
		else p=0;//没有子树,直接删除
		return;
	}
	val<tr[p].val?remove(tr[p].l,val):remove(tr[p].r,val);
	update(p);
	return;
}//删除节点

i.求节点的排名:

int getrank(int &p,int val)
{
	if(p==0)return 0;
	if(val==tr[p].val)return tr[tr[p].l].size+1;//直接返回
	if(val<tr[p].val)return getrank(tr[p].l,val);//向左子树查找
	return getrank(tr[p].r,val)+tr[tr[p].l].size+tr[p].cnt;//向右子树查找,并加上左子树和当前节点的大小
}//求节点的排名

j.求排名为rank的节点:

int getval(int &p,int rank)
{
	if(p==0)return inf;
	if(tr[tr[p].l].size>=rank)return getval(tr[p].l,rank);//向左查找
	if(tr[tr[p].l].size+tr[p].cnt>=rank)return tr[p].val;//确定排名为rank的节点就是当前节点
	return getval(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);//向右查找
}//求排名为rank的节点

k.求前驱:

int getpre(int val)
{
	int ans=1,p=root;//tr[1].val==-inf
	while(p)
	{
		if(val==tr[p].val)
		{
			if(tr[p].l>0)
			{
				p=tr[p].l;
				while(tr[p].r>0)p=tr[p].r;
				ans=p;
			}
			break;
		}//先向左走一步,再一直向右走
		if(tr[p].val<val&&tr[p].val>tr[ans].val)ans=p;//更新最优节点
		p=val<tr[p].val?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}//求前驱

l.求后继:

int getnext(int val)
{
	int p=root,ans=2;//tr[2].val==inf
	while(p)
	{
		if(val==tr[p].val)
		{
			if(tr[p].r>0)
			{
				p=tr[p].r;
				while(tr[p].l>0)p=tr[p].l;
				ans=p;
			}//先向右走一步,再一直向左走
			break;
		}
		if(tr[p].val>val&&tr[p].val<tr[ans].val)ans=p;//更新最优节点
		p=val<tr[p].val?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}//求后继

完整代码:

#include<cstdio>
#include<algorithm>
#define maxn 100005
#define inf 1000000007
using namespace std;

struct treap
{
	int l,r;
	int val,dat;//val是实际权值,dap是随机权值
	int size,cnt;//size是子树大小,cnt是元素个数
}tr[maxn];
int n;
int tot,root;

void update(int p)
{
	tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
}//更新子树大小

int New(int val)
{
	tot++;
	tr[tot].val=val;
	tr[tot].dat=rand();//赋予随机值
	tr[tot].cnt=tr[tot].size=1;
	return tot;
}//建立新节点

void zig(int &p)
{
	int q=tr[p].l;
	tr[p].l=tr[q].r;
	tr[q].r=p;
	p=q;
	update(tr[p].r);
	update(p);
}//右旋操作

void zag(int &p)
{
	int q=tr[p].r;
	tr[p].r=tr[q].l;
	tr[q].l=p;
	p=q;
	update(tr[p].l);
	update(p);
}//左旋操作

void build()
{
	New(-inf),New(inf);
	root=1;
	tr[1].r=2;
	update(root);
}//初始化操作

void insert(int &p,int val)
{
	if(p==0)
	{
		p=New(val);
		return;
	}
	if(val==tr[p].val)
	{
		tr[p].cnt++;
		update(p);
		return;
	}
	if(val<tr[p].val)
	{
		insert(tr[p].l,val);//插入左子树
		if(tr[p].dat<tr[tr[p].l].dat)zig(p);//不满足堆的性质,右旋
	}
	else
	{
		insert(tr[p].r,val);//插入右子树
		if(tr[p].dat<tr[tr[p].r].dat)zag(p);//不满足堆的性质,左旋
	}
	update(p);
}//插入节点

void remove(int &p,int val)
{
	if(p==0)return;
	if(val==tr[p].val)
	{
		if(tr[p].cnt>1)
		{
			tr[p].cnt--;
			update(p);
			return;
		}//有重复
		if(tr[p].l||tr[p].r)
		{
			if(tr[p].r==0||tr[tr[p].l].dat>tr[tr[p].r].dat)
			{
				zig(p);
				remove(tr[p].r,val);
			}//空右节点就把当前节点移到右节点
			else
			{
				zag(p);
				remove(tr[p].l,val);
			}//空左节点就把当前节点移到左节点
			update(p);
		}//不是叶子节点,向下旋转
		else p=0;//没有子树,直接删除
		return;
	}
	val<tr[p].val?remove(tr[p].l,val):remove(tr[p].r,val);
	update(p);
	return;
}//删除节点

int getrank(int &p,int val)
{
	if(p==0)return 0;
	if(val==tr[p].val)return tr[tr[p].l].size+1;//直接返回
	if(val<tr[p].val)return getrank(tr[p].l,val);//向左子树查找
	return getrank(tr[p].r,val)+tr[tr[p].l].size+tr[p].cnt;//向右子树查找,并加上左子树和当前节点的大小
}//求节点的排名

int getval(int &p,int rank)
{
	if(p==0)return inf;
	if(tr[tr[p].l].size>=rank)return getval(tr[p].l,rank);//向左查找
	if(tr[tr[p].l].size+tr[p].cnt>=rank)return tr[p].val;//确定排名为rank的节点就是当前节点
	return getval(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);//向右查找
}//求排名为rank的节点

int getpre(int val)
{
	int ans=1,p=root;//tr[1].val==-inf
	while(p)
	{
		if(val==tr[p].val)
		{
			if(tr[p].l>0)
			{
				p=tr[p].l;
				while(tr[p].r>0)p=tr[p].r;
				ans=p;
			}
			break;
		}//先向左走一步,再一直向右走
		if(tr[p].val<val&&tr[p].val>tr[ans].val)ans=p;//更新最优节点
		p=val<tr[p].val?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}//求前驱

int getnext(int val)
{
	int p=root,ans=2;//tr[2].val==inf
	while(p)
	{
		if(val==tr[p].val)
		{
			if(tr[p].r>0)
			{
				p=tr[p].r;
				while(tr[p].l>0)p=tr[p].l;
				ans=p;
			}//先向右走一步,再一直向左走
			break;
		}
		if(tr[p].val>val&&tr[p].val<tr[ans].val)ans=p;//更新最优节点
		p=val<tr[p].val?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}//求后继

int main()
{
	scanf("%d",&n);
	build();
	for(int i=1;i<=n;++i)
	{
		int opt,x;
		scanf("%d%d",&opt,&x);
		switch(opt)
		{
			case 1:
				insert(root,x);
				break;
			case 2:
				remove(root,x);
				break;
			case 3:
				printf("%d\n",getrank(root,x)-1);
				break;
			case 4:
				printf("%d\n",getval(root,x+1));
				break;
			case 5:
				printf("%d\n",getpre(x));
				break;
			case 6:
				printf("%d\n",getnext(x));
				break;
		}
	}
	return 0;
}

模板题:【模板】普通平衡树;

2.Splay平衡树

特点:无左右旋操作,可打标记。
缺点:难理解 Q A Q QAQ QAQ
a.建树:

struct node
{
	int fa,ch[2];
	int val,cnt;
	int size,rev;
}tr[maxn];//fa是父亲,son是儿子,val为节点的权值,cnt为节点重复的个数,size是节点的子树大小,rev为翻转标记;

b.返回儿子是左儿子还是右儿子:

int get(int x)
{
	int f=tr[x].fa;
	return x==tr[f].ch[1];
}//返回该节点是左儿子还是右儿子

c.更新子树大小:

void update(int x)
{
	int l=tr[x].ch[0],r=tr[x].ch[1];
	tr[x].size=tr[x].cnt+tr[l].size+tr[r].size;
}//更新节点的子树大小

d.标记下传(区间翻转操作可用):

void pushdown(int x)
{
	if(tr[x].rev)
	{
		int l=tr[x].ch[0],r=tr[x].ch[1];
		swap(tr[x].ch[0],tr[x].ch[1]);
		tr[l].rev^=1;
		tr[r].rev^=1;
		tr[x].rev=0;
	}
}//标记下传

e.旋转操作(关键):

void rotate(int x)
{
	int f=tr[x].fa,ff=tr[f].fa,k=get(x),s=tr[x].ch[k^1];
	tr[f].ch[k]=s;
	tr[s].fa=f;
	tr[ff].ch[get(f)]=x;
	tr[x].fa=ff;
	tr[x].ch[k^1]=f;
	tr[f].fa=x;
	update(x);
	update(f);
}//旋转操作

f.Splay操作(关键):

void splay(int x,int k=0)
{
	while(tr[x].fa!=k)
	{
		int f=tr[x].fa,ff=tr[f].fa;
		if(ff!=k)
		{
			if(get(x)==get(f))rotate(f);
			else rotate(x);
		}
		rotate(x);
	}
	if(!k)root=x;
}//将某一节点旋转到k节点的子节点

g.插入节点(顺便把节点变成根节点):

void insert(int x)
{
	int k=root,p=0;
	while(k&&tr[k].val!=x)
	{
		p=k;
		k=tr[k].ch[x>tr[k].val];
	}
	if(k)
	tr[k].cnt++;//有该节点了
	else
	{
		k=++tot;
		if(p)tr[p].ch[x>tr[p].val]=k;
		tr[k].ch[0]=tr[k].ch[1]=0;
		tr[k].fa=p;
		tr[k].val=x;
		tr[k].cnt=tr[k].size=1;
	}
	splay(k);
}//插入节点

h.删除节点:

void remove(int x)
{
	int last=getpre(x),next=getnext(x);
	splay(last);
	splay(next,last);//将x节点移到后继的左子节点
	int del=tr[next].ch[0];//提取该节点
	if(tr[del].cnt>1)
	{
		tr[del].cnt--;
		splay(del);
	}
	else
	{
		tr[next].ch[0]=0;
		update(next);
		update(root);
	}
}//删除

i.find操作:

void find(int x)
{
	int k=root;
	while(tr[k].ch[x>tr[k].val]&&tr[k].val!=x)
	k=tr[k].ch[x>tr[k].val];
	splay(k);
}//将最大的小于等于x的数所在的节点splay到根

j.查询排名为x的节点:

int getval(int x)
{
	int k=root;
	while(1)
	{
		pushdown(k);
		int l=tr[k].ch[0];
		if(l&&x<=tr[l].size)
		k=tr[k].ch[0];
		else if(x>tr[l].size+tr[k].cnt)
		{
			x-=tr[l].size+tr[k].cnt;
			k=tr[k].ch[1];
		}
		else
		return k;
	}
}//查询排名x的节点

k.求前驱:

int getpre(int x)
{
	find(x);
	if(tr[root].val<x)return root;
	int k=tr[root].ch[0];
	while(tr[k].ch[1])
	k=tr[k].ch[1];
	return k;
}//求x的前驱

l.求后继:

int getnext(int x)
{
	find(x);
	if(tr[root].val>x)return root;
	int k=tr[root].ch[1];
	while(tr[k].ch[0])
	k=tr[k].ch[0];
	return k;
}//求x的后继

m.区间翻转打标记:

void reverse(int l,int r)
{
	int x=getval(l),y=getval(r+2);
	splay(x);//将x转到根
	splay(y,x);//将y转到x的子节点 
	tr[tr[y].ch[0]].rev^=1;
}//区间翻转

完整代码:

#include<cstdio>
#include<algorithm>
#define oo 0x3f3f3f3f
#define maxn 100005
using namespace std;

struct node
{
	int fa,ch[2];
	int val,cnt;
	int size,rev;
}tr[maxn];//fa是父亲,son是儿子,val为节点的权值,cnt为节点重复的个数,size是节点的子树大小,rev为翻转标记;
int root,tot;
int n,op,x;

int get(int x)
{
	int f=tr[x].fa;
	return x==tr[f].ch[1];
}//返回该节点是左儿子还是右儿子

void update(int x)
{
	int l=tr[x].ch[0],r=tr[x].ch[1];
	tr[x].size=tr[x].cnt+tr[l].size+tr[r].size;
}//更新节点的子树大小

void pushdown(int x)
{
	if(tr[x].rev)
	{
		int l=tr[x].ch[0],r=tr[x].ch[1];
		swap(tr[x].ch[0],tr[x].ch[1]);
		tr[l].rev^=1;
		tr[r].rev^=1;
		tr[x].rev=0;
	}
}//标记下传

void rotate(int x)
{
	int f=tr[x].fa,ff=tr[f].fa,k=get(x),s=tr[x].ch[k^1];
	tr[f].ch[k]=s;
	tr[s].fa=f;
	tr[ff].ch[get(f)]=x;
	tr[x].fa=ff;
	tr[x].ch[k^1]=f;
	tr[f].fa=x;
	update(x);
	update(f);
}//旋转操作

void splay(int x,int k=0)
{
	while(tr[x].fa!=k)
	{
		int f=tr[x].fa,ff=tr[f].fa;
		if(ff!=k)
		{
			if(get(x)==get(f))rotate(f);
			else rotate(x);
		}
		rotate(x);
	}
	if(!k)root=x;
}//将某一节点旋转到k节点的子节点

void insert(int x)
{
	int k=root,p=0;
	while(k&&tr[k].val!=x)
	{
		p=k;
		k=tr[k].ch[x>tr[k].val];
	}
	if(k)
	tr[k].cnt++;//有该节点了
	else
	{
		k=++tot;
		if(p)tr[p].ch[x>tr[p].val]=k;
		tr[k].ch[0]=tr[k].ch[1]=0;
		tr[k].fa=p;
		tr[k].val=x;
		tr[k].cnt=tr[k].size=1;
	}
	splay(k);
}//插入节点

void find(int x)
{
	int k=root;
	while(tr[k].ch[x>tr[k].val]&&tr[k].val!=x)
	k=tr[k].ch[x>tr[k].val];
	splay(k);
}//将最大的小于等于x的数所在的节点splay到根

int getval(int x)
{
	int k=root;
	while(1)
	{
		pushdown(k);
		int l=tr[k].ch[0];
		if(l&&x<=tr[l].size)
		k=tr[k].ch[0];
		else if(x>tr[l].size+tr[k].cnt)
		{
			x-=tr[l].size+tr[k].cnt;
			k=tr[k].ch[1];
		}
		else
		return k;
	}
}//查询排名x的节点

void reverse(int l,int r)
{
	int x=getval(l),y=getval(r+2);
	splay(x);//将x转到根
	splay(y,x);//将y转到x的子节点 
	tr[tr[y].ch[0]].rev^=1;
}//区间翻转

int getpre(int x)
{
	find(x);
	if(tr[root].val<x)return root;
	int k=tr[root].ch[0];
	while(tr[k].ch[1])
	k=tr[k].ch[1];
	return k;
}//求x的前驱

int getnext(int x)
{
	find(x);
	if(tr[root].val>x)return root;
	int k=tr[root].ch[1];
	while(tr[k].ch[0])
	k=tr[k].ch[0];
	return k;
}//求x的后继

void remove(int x)
{
	int last=getpre(x),next=getnext(x);
	splay(last);
	splay(next,last);//将x节点移到后继的左子节点
	int del=tr[next].ch[0];//提取该节点
	if(tr[del].cnt>1)
	{
		tr[del].cnt--;
		splay(del);
	}
	else
	{
		tr[next].ch[0]=0;
		update(next);
		update(root);
	}
}//删除

int main()
{
	scanf("%d",&n);
	insert(oo);
	insert(-oo);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d",&op,&x);
		switch (op)
		{
			case 1:insert(x);break;
			case 2:remove(x);break;
			case 3:find(x);printf("%d\n",tr[tr[root].ch[0]].size);break;
			case 4:printf("%d\n",tr[getval(x+1)].val);break;
			case 5:printf("%d\n",tr[getpre(x)].val);break;
			case 6:printf("%d\n",tr[getnext(x)].val);break;
		}
	}
	return 0;
}

模板题:
1.【模板】普通平衡树;
2.【模板】文艺平衡树(Splay);(区间翻转模板题)

  • 习题

1.[TJOI2013]最长上升子序列;
2.[NOI2005]维护数列;


LCT

  • 概念
    LCT的全称是Link Cut Tree(动态树),支持动态加边,动态删边和换根操作。大体是一颗Splay,核心是实链剖分。

  • 代码
    1.建树:

struct node
{
	int fa,size,rev,ch[2];
}tr[maxn];//size记录字数异或和,rev记录翻转标记

2.信息更新:

void update(int x)
{
	int l=tr[x].ch[0],r=tr[x].ch[1];
	tr[x].size=tr[l].size^tr[r].size^val[x];
}//更新节点的子树异或和

3.标记下传:

void filp(int x)
{
	swap(tr[x].ch[0],tr[x].ch[1]);
	tr[x].rev^=1;
}

void pushdown(int x)
{
	if(tr[x].rev)
	{
		int l=tr[x].ch[0],r=tr[x].ch[1];
		if(l)filp(l);
		if(r)filp(r);
		tr[x].rev=0;
	}
}//标记下传

4.旋转操作:

void rotate(int x)
{
	int f=tr[x].fa,ff=tr[f].fa,k=(tr[f].ch[1]==x),s=tr[x].ch[k^1];
	if(get(f))tr[ff].ch[tr[ff].ch[1]==f]=x;
	if(s)tr[s].fa=f;
	tr[f].ch[k]=s;
	tr[x].ch[k^1]=f;
	tr[x].fa=ff;
	tr[f].fa=x;
	update(x);
	update(f);
}//旋转操作(与splay略有不同)

5.Splay操作:

void splay(int x)
{
	int y=x,top=0;
	hep[++top]=y;
	while(get(y))
	{
		y=tr[y].fa;
		hep[++top]=y;
	}
	while(top)
	pushdown(hep[top--]);
	while(get(x))
	{
		y=tr[x].fa;
		top=tr[y].fa;
		if(get(y))
		{
			if((tr[y].ch[0]==x)^(tr[top].ch[0]==y))rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	update(x);
	return;
}//将x节点旋转到splay中的根

6.将x到树根的路径设为实链

void access(int x)
{
	int y=0;
	while(x)
	{
		splay(x);
		tr[x].ch[1]=y;
		update(x);
		y=x;
		x=tr[x].fa;
	}
}//将x到树根的路径设为实链

7.找树根:

int findroot(int x)
{
	access(x);
	splay(x);
	while(tr[x].ch[0])
	{
		pushdown(x);
		x=tr[x].ch[0];
	}
	return x;
}//找树根

8.把x设为树根:s

void makeroot(int x)
{
	access(x);
	splay(x);
	filp(x);
}//把x设为树根

9.split操作:

void split(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
}

10.动态加边:

void link(int x,int y)
{
	makeroot(x);
	if(findroot(y)!=x)tr[x].fa=y;
}//x,y建边

11.动态删边:

void cut(int x,int y)
{
	makeroot(x);
	if(findroot(y)==x&&tr[x].fa==y&&!tr[x].ch[1])
	{
		tr[x].fa=tr[y].ch[0]=0;
		update(y);
	}
	return;
}//去掉x,y之间的边

完整代码:

#include<cstdio>
#include<algorithm>
#define maxn 300005
#define oo 0x3f3f3f3f
using namespace std;

struct node
{
	int fa,size,rev,ch[2];
}tr[maxn];//size记录字数异或和,rev记录翻转标记
int n,m;
int val[maxn],hep[maxn];

int get(int x)
{
	int f=tr[x].fa;
	return tr[f].ch[0]==x||tr[f].ch[1]==x;
}//返回该节点是左儿子还是右儿子

void update(int x)
{
	int l=tr[x].ch[0],r=tr[x].ch[1];
	tr[x].size=tr[l].size^tr[r].size^val[x];
}//更新节点的子树异或和

void filp(int x)
{
	swap(tr[x].ch[0],tr[x].ch[1]);
	tr[x].rev^=1;
}

void pushdown(int x)
{
	if(tr[x].rev)
	{
		int l=tr[x].ch[0],r=tr[x].ch[1];
		if(l)filp(l);
		if(r)filp(r);
		tr[x].rev=0;
	}
}//标记下传

void rotate(int x)
{
	int f=tr[x].fa,ff=tr[f].fa,k=(tr[f].ch[1]==x),s=tr[x].ch[k^1];
	if(get(f))tr[ff].ch[tr[ff].ch[1]==f]=x;
	if(s)tr[s].fa=f;
	tr[f].ch[k]=s;
	tr[x].ch[k^1]=f;
	tr[x].fa=ff;
	tr[f].fa=x;
	update(x);
	update(f);
}//旋转操作(与splay略有不同)

void splay(int x)
{
	int y=x,top=0;
	hep[++top]=y;
	while(get(y))
	{
		y=tr[y].fa;
		hep[++top]=y;
	}
	while(top)
	pushdown(hep[top--]);
	while(get(x))
	{
		y=tr[x].fa;
		top=tr[y].fa;
		if(get(y))
		{
			if((tr[y].ch[0]==x)^(tr[top].ch[0]==y))rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	update(x);
	return;
}//将x节点旋转到splay中的根

void access(int x)
{
	int y=0;
	while(x)
	{
		splay(x);
		tr[x].ch[1]=y;
		update(x);
		y=x;
		x=tr[x].fa;
	}
}//将x到树根的路径设为实链

int findroot(int x)
{
	access(x);
	splay(x);
	while(tr[x].ch[0])
	{
		pushdown(x);
		x=tr[x].ch[0];
	}
	return x;
}//找树根

void makeroot(int x)
{
	access(x);
	splay(x);
	filp(x);
}//把x设为树根

void split(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
}

void link(int x,int y)
{
	makeroot(x);
	if(findroot(y)!=x)tr[x].fa=y;
}//x,y建边

void cut(int x,int y)
{
	makeroot(x);
	if(findroot(y)==x&&tr[x].fa==y&&!tr[x].ch[1])
	{
		tr[x].fa=tr[y].ch[0]=0;
		update(y);
	}
	return;
}//去掉x,y之间的边

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	scanf("%d",&val[i]);
	for(int i=1;i<=m;++i)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		switch (op)
		{
			case 0:split(x,y);printf("%d\n",tr[y].size);break;
			case 1:link(x,y);break;
			case 2:cut(x,y);break;
			case 3:splay(x);val[x]=y;break;
		}
	}
	return 0;
}

模板题:
1.【模板】Link Cut Tree (动态树)
2.[SDOI2008]洞穴勘测


树套树


并查集

  • 定义
    并查集可以理解为合并+查找+集合。可以处理两个元素是否在同一个集合里面,或者把两个元素加入到同一个集合里面。

  • 代码

1.查找父亲:

int find(int x)
{
	while(father[x]!=x)x=father[x];
	return father[x];
}

路径压缩:

int find(int x)
{
    if(father[x]!=x)return father[x]=find(father[x]);
    return father[x];
}

2.合并集合:

void unionn(int x,int y)
{
	x=find(x),y=find(y);
	father[y]=x;
}

3.询问两个元素是否在同一个集合中:

int judge(int x,int y)
{
	x=find(x),y=find(y);
	if(x==y)
	return 1;
	else return 0;
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,q,father[20005],ans[1000005];
int find(int x)
{
	while(father[x]!=x)x=father[x];
	return father[x];
}
void unionn(int x,int y)
{
	x=find(x),y=find(y);
	father[y]=x;
}
int judge(int x,int y)
{
	x=find(x),y=find(y);
	if(x==y)
	return 1;
	else return 0;
}
int main()
{
	scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    father[i]=i;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		unionn(a,b);
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		ans[i]=judge(a,b);
	}
	for(int i=1;i<=q;i++)
	if(ans[i]==1)
	printf("Yes\n");
	else printf("No\n");
	return 0;
}

模板题:亲戚
还有一道带权并查集好题:一盘大棋


线段树分治


CDQ分治

#include<cstdio>
#include<algorithm>
#define maxn 200005
using namespace std;

struct node
{
	int a,b,c,cnt,ans;
}s1[maxn],s2[maxn];
int n,m,k,mx,top,su[maxn];
int c[maxn];//树状数组

bool cmp1(node x,node y)
{
	if(x.a==y.a)
	{
		if(x.b==y.b)return x.c<y.c;
		else return x.b<y.b;
	}
	else return x.a<y.a;
}//第一维排序

bool cmp2(node x,node y)
{
	if(x.b==y.b)
	return x.c<y.c;
	else return x.b<y.b;
}//第二维排序

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

void add(int x,int y)
{
	while(x<=mx)
	{
		c[x]+=y;
		x+=lowbit(x);
	}
}//树状数组单点加

int query(int x)
{
	int sum=0;
	while(x)
	{
		sum+=c[x];
		x-=lowbit(x);
	}
	return sum;
}//求单点前缀和

void cdq(int l,int r)
{
	if(l==r)return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);//类似于归并排序
	sort(s2+l,s2+mid+1,cmp2);
	sort(s2+mid+1,s2+r+1,cmp2);//第二维为关键字排序
	int i,j=l;
	for(i=mid+1;i<=r;++i)
	{
		while(s2[i].b>=s2[j].b&&j<=mid)
		{
			add(s2[j].c,s2[j].cnt);//在s2[j]位置加上s2[j]的个数
			j++;
		}
		s2[i].ans+=query(s2[i].c);//保证树状数组里的数一定符合条件
	}//类似归并
	for(i=l;i<j;++i)
	add(s2[i].c,-s2[i].cnt);//清空树状数组
}//cdq分治

int main()
{
	scanf("%d%d",&n,&k);
	mx=k;//树状数组的区间
	for(int i=1;i<=n;++i)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		s1[i].a=a;
		s1[i].b=b;
		s1[i].c=c;
	}//初始化输入
	sort(s1+1,s1+1+n,cmp1);//第一维为关键字排序
	for(int i=1;i<=n;++i)
	{
		top++;
		if(s1[i].a!=s1[i+1].a||s1[i].b!=s1[i+1].b||s1[i].c!=s1[i+1].c)
		{
			m++;
			s2[m].a=s1[i].a;
			s2[m].b=s1[i].b;
			s2[m].c=s1[i].c;
			s2[m].cnt=top;
			top=0;
		}
	}//第一维已有序,合并相同节点
	cdq(1,m);//caq分治
	for(int i=1;i<=m;++i)
	su[s2[i].ans+s2[i].cnt-1]+=s2[i].cnt;
	for(int i=0;i<n;++i)
	printf("%d\n",su[i]);
	return 0;
}

整体二分


点分治


分块

  • 思想
    将一个长的区间分成很多短的区间,统计区间内的答案。求答案时直接加上整块的答案,对于非整块,暴力统计。复杂度 O ( n n ) O(n\sqrt n) O(nn ),可处理在线操作。

  • 代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 50005
#define maxm 305
using namespace std;

struct node
{
	int l,r;
}blo[maxm];//记录块的范围
struct tree
{
	int past,now,num;
}a[maxn];
int n,m,tot,sum,nw,ans,len,num_all;
int cnt[maxm][maxn],all[maxm][maxn],tong[maxn];//cnt[i][j]表示第i块j(离散化后)的个数,tong[i]表示离散化后i对应的原数
int pos[maxn],wai[maxn],tab[maxn];;//pos[i]表示节点i所在的块

inline int read()
{
	char ch;
	while((ch=getchar())<'0'||ch>'9');
	int res=ch-48;
	while((ch=getchar())>='0'&&ch<='9')
	res=res*10+ch-48;
	return res;
} //读入优化

void print(int x)
{
    if(x<0)//负数
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}

bool cmp1(tree x,tree y)
{
	return x.past<y.past;
}

bool cmp2(tree x,tree y)
{
	return x.num<y.num;
}

int ask(int l,int r)
{
	memset(wai,0,sizeof(wai));
	int p=pos[l],q=pos[r],ans=0,mx,nww=0;
	if(p==q)
	{
		for(int i=l;i<=r;++i)
		wai[a[i].now]++;
		for(int i=1;i<=num_all;++i)
		{
			if(wai[i]>ans)
			{
				ans=wai[i];
				mx=i;
			}
		}
	}//在同一块内
	else
	{
		for(int j=1;j<=num_all;++j)
		wai[j]=all[q-1][j]-all[p][j];
		for(int i=l;i<=blo[p].r;++i)
		wai[a[i].now]++;
		for(int i=blo[q].l;i<=r;++i)
		wai[a[i].now]++;
		for(int i=1;i<=num_all;++i)
		{
			if(wai[i]>ans)
			{
				ans=wai[i];
				mx=i;
			}
		}
	}
	return tab[mx];
}//分块

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)
	{
		a[i].past=read();
		a[i].num=i;
	}
	sort(a+1,a+1+n,cmp1);
	for(int i=1;i<=n;++i)
	{
		a[i].now=a[i-1].now;
		if(a[i].past!=a[i-1].past)
		{
			a[i].now++;
			num_all++;
			tab[num_all]=a[i].past;
		}
	}//离散化
	sort(a+1,a+1+n,cmp2);
	tot=sqrt(n);
	if(tot*tot<n)tot++;//统计块的个数
	for(int i=1;i<=n;i+=tot)
	{
		blo[++nw].l=i;
		blo[nw].r=i+tot-1;
	}
	if(nw<tot)
	{
		blo[++nw].l=nw*tot+1;
		blo[nw].r=n;
	}//统计块的范围
	if(blo[nw].r>n)blo[nw].r=n;
	for(int i=1;i<=tot;++i)
	{
		for(int j=blo[i].l;j<=blo[i].r;++j)
		{
			cnt[i][a[j].now]++;
			pos[j]=i;
		}
	}//预处理出每个块不同数出现的个数
	for(int i=1;i<=tot;++i)
	{
		for(int j=1;j<=num_all;++j)
		all[i][j]=all[i-1][j]+cnt[i][j];
	}
	for(int i=1;i<=m;++i)
	{
		int l,r;
		l=read();r=read();
		l=(l+ans-1)%n;
		r=(r+ans-1)%n;
		l++;r++;
		if(l>r)
		swap(l,r);
		ans=ask(l,r);
		print(ans);
		putchar('\n');
	}
	return 0;
}

模板题:
1.蒲公英
2.弹飞绵羊


莫队

  • 思想
    存储答案,将答案排序,之后暴力更新答案。复杂度玄学,只能离线操作。

  • 代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define maxn 50005
using namespace std;

struct node
{
	ll l,r,num;
}que[maxn];
struct answer
{
	ll mom,son;
}ans[maxn];
ll n,m,tot,mom,sum,total,cntt;
ll co[maxn],son[maxn],cnt[maxn];

bool cmp(node x,node y)
{
	return (x.l/tot)^(y.l/tot)?x.l<y.l:((x.l/tot)&1)?x.r<y.r:x.r>y.r;
}//玄学奇偶性排序

ll gcd(ll x,ll y)
{
	return (!y)?x:gcd(y,x%y);
}

void add(ll x)
{
	if(!x)return;
	ll v=co[x];
	total+=2*cnt[v];
	cnt[v]++;
	mom+=2*sum;
	sum++;
}

void del(ll x)
{
	if(!x)return;
	ll v=co[x];
	total-=2*cnt[v]-2;
	cnt[v]--;
	mom-=2*sum-2;
	sum--;
}

int main()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;++i)
	scanf("%lld",&co[i]);
	for(ll i=1;i<=m;++i)
	{
		ll l,r;
		scanf("%lld%lld",&l,&r);
		if(l==r)
		{
			ans[i].son=0;
			ans[i].mom=1;
			continue;
		}
		cntt++;
		que[cntt].l=l;
		que[cntt].r=r;
		que[cntt].num=i;
	}
	tot=n/sqrt(cntt*2/3);
	sort(que+1,que+1+cntt,cmp);
	ll l=0,r=0;
	for(ll i=1;i<=m;++i)
	{
		ll ql=que[i].l,qr=que[i].r,nw=que[i].num;
		while(l<ql)del(l++);
		while(l>ql)add(--l);
		while(r<qr)add(++r);
		while(r>qr)del(r--);
		ans[nw].mom=mom;
		ans[nw].son=total;
	}
	for(ll i=1;i<=m;++i)
	{
		ll r=gcd(ans[i].son,ans[i].mom);
		ans[i].son/=r;
		ans[i].mom/=r;
		printf("%lld/%lld\n",ans[i].son,ans[i].mom);
	}
	return 0;
}

模板题: [国家集训队]小Z的袜子

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值