嗯 昨天某题TLE了一晚上
搜索优化方式的时候看到了这个
于是 今天学了下 - -
觉得上面那个教程的图蛮好的
这些讲的挺详细的
嗯 说下理解 也不知道是不是对的 = =
错了话 继续改好了 先记录一下 ^^
主席树的主体是一棵线段树
有点像是一堆树被拼在一起 共同使用某些部分
嗯 先画张图
这个是一棵空的线段树
假设我们有一个数列 ARR[N] = { 1,2,8,3,7,6,5,4}
现在要查询 某段区间 [l,r]中值小于K的元素的个数 其中集合元素的大小 [1~n]
这个时候 如果数据量较大的时候直接扫一般会超时
但是我们可以这样考虑
如果 我们构建N棵线段树 第i棵线段树以arr[1] ~~arr[ i] 的元素建立
这样 关于统计这个区间值小于K 的问题就可变为 NUM = 第 r 棵线段树 的 区间 [1,k ] 元素数量 - 第 l - 1 棵线段树的 区间 [1,k ] 元素数量
但是 这样一来 可以发现 很容易 MLE
于是 我们可以分析一下
会发现 这里的每棵线段树的形态都是一样的 (因为维护的都是 arr 的元素值的个数)
这样以来 可以对着图看
我们现在构建第一棵线段树
粗线条表示的是每棵线段树将会在空树基础上统计更改的路径
接着是第二棵
第三棵
等等等
通过这几张图
可以发现 第 i 棵线段树的数值 实际上相当于是 在第 i - 1 棵线段上完成更新的结果
而我们并不能直接在第 i - 1 棵树上直接更新 (直接更新完之后 第 i - 1棵线段树的数值就不准确了)
那么 通过观察 可以发现 第 i 棵线段树 仅 和 第 i - 1 棵线段树 有一条链上的不同
那么我们就可以 设想 在建立第 i 棵线段树上 并不完全的新建一个 可以 与上棵线段树共用除了要更新的那条链上的节点
就像这张图 其中 i = 2
灰色的是 i = 2 的线段树 用 16 17 18 19 代替原来的紫色路径上的点 (但是节点的区间意义是一样的)(说的是更上面的 第 i = 2 建立的线段树)
而其他颜色的路径不需要更新 可以利用 就像新建立的代表 [1,4] 的节点 17 它的右区间没有更新
他就可以使用 代表[3,4]区间的 5 号节点 也就是他的右子树 还是 5 号结点
再下一棵线段树就在这棵树的基础上做连接判断
等等等
(这种树 用数组模拟指针其实也蛮方便的 (- -.))
这样每次建树 最多只要 加上根节点开始的最大深度的那条链的大小 优化了空间
这大概是 主席树的大致形态
然后使用的话
这个是统计 第 i 棵树 [l,r]之间的元素个数的
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#define pi acos(-1.0)
#define inf 1<<29
#define INF 0x3fffffff
#define zero 1e-8
const int li[]={-1,0,1,0};
const int lj[]={0,-1,0,1};
using namespace std;
const int N= 1e5+107;
int arr[N];
struct lo{
int l,r,data,node;
void show(){
cout<<"l: "<<l<<" r: "<<r<<" data: "<<data<<" node: "<<node<<endl;
}
}cmtree[N*100];
int root[N],ROOT;
int pt;
int sum;
void init(){
memset(cmtree,0,sizeof(cmtree));
pt=0;
}
void build(int from,int node,int left,int right,int arrow){
++pt;
int now=pt;
if (arrow==1) cmtree[from].l=pt;
else cmtree[from].r=pt;
cmtree[pt].node=node;
if (left==right){
cmtree[pt].l=cmtree[pt].r=-1;
cmtree[pt].data=0;
return;
}
build(now,node*2,left,(left+right)/2,1);
build(now,node*2+1,(left+right)/2+1,right,2);
return;
}
void FIND(int node,int left,int right,int next){
cout<<" left: "<<left<<" right: "<<right<<endl<<"-----------"<<endl;
cmtree[next].show();
cout<<endl<<endl;
if (left==right) return ;
FIND(node*2,left,(left+right)/2,cmtree[next].l);
FIND(node*2+1,(left+right)/2+1,right,cmtree[next].r);
}
void update(int node,int left,int right,int from,int mod,int data,int arrow){
++pt;
int now=pt;
if (arrow==1) cmtree[from].l=pt;
else cmtree[from].r=pt;
int mid=(left+right)/2;
cmtree[pt].node=node;
if (left==right){
cmtree[pt].data=cmtree[mod].data+1;
cmtree[pt].l=cmtree[pt].r=-1;
return;
}
if (data<=mid) {
update(node*2,left,mid,pt,cmtree[mod].l,data,1);
cmtree[now].r=cmtree[mod].r;
}
else {
update(node*2+1,mid+1,right,pt,cmtree[mod].r,data,2);
cmtree[now].l=cmtree[mod].l;
}
cmtree[now].data=cmtree[cmtree[now].l].data+cmtree[cmtree[now].r].data;
ROOT=now;
}
void query(int node,int left,int right,int Begin,int End,int next){
if (left>End||right<Begin) return ;
if (left>=Begin&&right<=End) {
sum+=cmtree[next].data;
return;
}
int mid=(left+right)/2;
query(node*2,left,mid,Begin,End,cmtree[next].l);
query(node*2+1,mid+1,right,Begin,End,cmtree[next].r);
}
int main()
{
int n;
while(~scanf("%d",&n)){
init();
for (int i=1;i<=n;++i){
scanf("%d",&arr[i]);
}
pt=0;
root[0]=pt;
build(root[0],1,1,n,1);
for (int i=1;i<=n;++i){
root[i]= ++pt;
update(1,1,n,root[i],root[i-1]+1,arr[i],1);
}
int q;
scanf("%d",&q);
for (int i=0;i<q;++i){
int l,r,k;
sum=0;
scanf("%d %d %d",&l,&r,&k);
query(1,1,n,l,r,root[k]+1);
int ans=sum;
cout<<ans<<endl;
}
}
return 0;
}
嗯 最后感觉 数据结构的使用 大概是把问题处理成某种状态后再使用数据结构优化
这有一题可以看看
hdu 5790 Prefix
题目链接 点击打开链接
AC代码 点击打开链接