栈
- 栈的定义:
栈是一种先进先出的数据结构。建立如下:
#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;
}
- 例题
1.【模板】左偏树(可并堆);
2.Monkey King;
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;
}
- 例题:
1.【模板】ST表;
树状数组
-
作用
快速求前缀和、区间和。 -
操作
支持修改 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.求逆序对;
3.作为辅助算法在一些高级题目中出现,减少时间复杂度。 -
进阶题:
1.【模板】三维偏序(陌上花开);
线段树
-
定义
一种数据结构,一个节点存储一个区间的信息。 -
思想
二分。 -
应用
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.【模板】可持久化数组(可持久化线段树/平衡树);
- 补充
值域线段树+线段树合并(都在这道题里了[HNOI2012]永无乡)
#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.【模板】树链剖分;
2.[ZJOI2008]树的统计;
3.[HAOI2015]树上操作;
非裸题:
1.月下“毛景树”;
2.[AHOI2005]航线规划;
平衡树
-
定义
平衡树是一种高级数据结构,满足二叉查找树的性质(即左儿子的权值小于父节点,右儿子的权值大于父节点)。
注:这里的权值有两种定义,一是以节点的大小为权值,二是以节点的编号为权值。 -
代码
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分治
-
定义
可以用来处理三维偏序问题。详见我的博客:陌上花开——浅谈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;
}
莫队
-
思想
存储答案,将答案排序,之后暴力更新答案。复杂度玄学,只能离线操作。 -
代码
#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的袜子;