简直卧槽了,充满RE的一天,没有太完成目标,算上昨天刷的一道主席树一共五道,算是入门了吧。
主席树可以认为是一种前缀和,表示的是每一个数出现的次数(当然必须要离散化),一种类似线段树的建法,为了节省空间就跟可持久化线段树一样了,这样一开始建出一个空树,每次就相当于在上一棵树的基础上进行修改,空间就是O(n log n)的了(还是很大呀!!!),写法跟前缀和非常像。
这样我们在查询区间【l,r】的第k小值时,就从l-1和r出发,向下找类似平衡树的招法,具体看代码好了,复杂度O(log n)的。
如果加入了修改操作怎么办呢?
我们来类比一下前缀和,很明显要维护带修改的前缀和就要使用树状数组,那么我们带修改的主席树是否也能用树状数组的思想维护呢?答案是肯定的。
这样的话,每次修改需要修改O(log n)棵主席树,空间复杂度就变成了O(n log^2 n),查询的时候,提前处理出需要查询的所有子树(log n级别),之后一起向下查询即可,复杂度O(log^2 n)。
bzoj3524
主席树裸题,来练模板的,就是查询第k小数,然后判断一下次数就好了。
#include
#include
#include
#include
#include
#include
#define maxn 500010
#define maxm 10000010
using namespace std;
struct yts
{
int x,id;
}a[maxn];
int lch[maxm],rch[maxm],cnt[maxm],root[maxn];
int n,f[maxn],T,tot;
bool cmp(yts a,yts b)
{
return a.xk) return query(lch[root1],lch[root2],l,mid,k);
else if (cnt[rch[root2]]-cnt[rch[root1]]>k) return query(rch[root1],rch[root2],mid+1,r,k);
else return 0;
}
int main()
{
scanf("%d%d",&n,&T);
for (int i=1;i<=n;i++) scanf("%d",&a[i].x);
for (int i=1;i<=n;i++) a[i].id=i;
sort(a+1,a+n+1,cmp);
int num=1;f[a[1].id]=1;
for (int i=2;i<=n;i++)
{
if (a[i].x!=a[i-1].x) num++;
f[a[i].id]=num;
}
tot=0;root[0]=lch[0]=rch[0]=cnt[0]=0;
for (int i=1;i<=n;i++)
root[i]=modify(root[i-1],1,num,f[i]);
while (T--)
{
int l,r;
scanf("%d%d",&l,&r);
int ans=query(root[l-1],root[r],1,num,(r-l+1)/2);
printf("%d\n",ans);
}
return 0;
}
bzoj1901
带修改的区间第k小,树状数组套主席树解决,注意空间是O(n log^2 n+m log^2 n)的。一开始可以O(n log n)建出n棵主席树,然后树状数组只维护变化就好了,这道题没有写这种算法,但是在后面有应用,空间复杂度优化为O(n log n+m log^2 n),然后注意查询的时候把所有要查询的树都提出来,一起向下查。
#include
#include
#include
#include
#include
#include
#include
bzoj2588
这次是不带修改的树上第k小,可以不用树状数组,其实这道题可以直接把主席树建在树上,而我写的是把主席树建在dfs序上,这样完全转化为线性问题,不过也会出现问题,注意:每次询问拆成4棵树的查询,root[x],root[y],root[lca(x,y)],root[fa[lca(x,y)]],一开始写成了root[lca(x,y)-1],发现RE了,后来想一下是错误的,因为这样会把前面的出栈节点也算上,复杂度O(n log n)。还有,要在一开始建主席树的时候就处理出栈序,不然会影响后面的主席树(类似你算完后面所有的前缀和后,又要修改前面的前缀和是不对的)。
#include
#include
#include
#include
#include
#include
#define maxn 8001000
#include
using namespace std;
struct yts
{
int x,id;
}a[100010];
int cnt[maxn],lch[maxn],rch[maxn];
int root[100010],e[100010],f[100010];
int head[100010],next[200010],to[200010];
int fa[100010][21],dep[100010],in[100010],out[100010],w[100010],rank[100010];
int b[5];
vector c[100010];
int num,tot,n,T,mx,num1,z;
bool cmp(yts a,yts b)
{
return a.x=0;i--)
if (d&(1<dep[y]) x=go_up(x,dep[x]-dep[y]);
else y=go_up(y,dep[y]-dep[x]);
if (x==y) return x;
for (int i=19;i>=0;i--)
if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int modify(int pre,int l,int r,int x,int f)
{
int now=++tot;
if (l==r)
{
cnt[now]=cnt[pre]+f;lch[now]=rch[now]=0;
}
else
{
int mid=(l+r)/2;
if (x<=mid)
{
rch[now]=rch[pre];lch[now]=modify(lch[pre],l,mid,x,f);
}
else
{
lch[now]=lch[pre];rch[now]=modify(rch[pre],mid+1,r,x,f);
}
cnt[now]=cnt[lch[now]]+cnt[rch[now]];
}
return now;
}
int query(int l,int r,int k)
{
if (l==r) return l;
int sum=cnt[lch[b[1]]]+cnt[lch[b[2]]]-cnt[lch[b[3]]]-cnt[lch[b[4]]];
int mid=(l+r)/2;
if (sum>=k)
{
for (int i=1;i<=4;i++) b[i]=lch[b[i]];
return query(l,mid,k);
}
else
{
for (int i=1;i<=4;i++) b[i]=rch[b[i]];
return query(mid+1,r,k-sum);
}
}
int main()
{
scanf("%d%d",&n,&T);
for (int i=1;i<=n;i++) scanf("%d",&a[i].x);
//离散化
for (int i=1;i<=n;i++) a[i].id=i;
sort(a+1,a+n+1,cmp);
mx=1;f[a[1].id]=1;rank[1]=a[1].x;
for (int i=2;i<=n;i++)
{
if (a[i].x!=a[i-1].x) rank[++mx]=a[i].x;
f[a[i].id]=mx;
}
//树上预处理
for (int i=1;i
bzoj1146
最丧病的题目,没有之一,带修改树上第k大,还卡空间。
首先不管卡空间,按照dfs序建主席树,用树状数组维护,然后把第k大变成第k小,到这一步和上一道题类似。对于卡空间的事情太丧病了,要先在树上建好主席树后,然后树状数组维护变化量,然后每次询问的时候,把原来的值在加入询问的树中。
#include
#include
#include
#include
#include
#include
#include
bzoj3932
可算找着个好一点的了,这道题还要维护一个前缀和,然后就没有什么难的了,前面调了好久,才发现是要维护前缀和,不是直接查第k小,直接呵呵了。
#include
#include
#include
#include
#include
#include
#include
#include
从线性无修改到线性有修改到树上无修改到树上有修改到维护特殊量,主席树也算是学了不少了吧,注意事项写在下面:
1.做题时尤其要注意空间限制,对于有修改的题目,刚开始建树的时候可以空间优化。
2.满足区间减法的量应该可以用主席树维护,不过貌似没有见过这种题,主席树全都是维护第k小呀?!
3.树状数组套主席树时,先把所有的要查询的树提取出来,然后一起向下查询。
4.在树上建主席树和在dfs序上建主席树是等价的,尽量在树上建。
5.注意dfs序和树链剖分的区别,dfs序可以维护支持区间减法的量。
6.对带修改的题目的把控,主要还在树状数组的理解,要明白树状数组维护的是什么。
就这样吧,明天学一下背包九讲,然后做几道题复习一下主席树。