一、线段树
1.线段树是一种用于区间处理的数据结构,用二叉树来构造。树的每个结点代表线段[L,R]。当L==R时代表这个结点只有一个点,即为叶子结点;如果L<R则代表这个结点是一个区间,还可以继续往下分为左儿子和右儿子。
线段树的操作次数是(log 2 n),复杂度优于直接查找。
通过树结点的延伸可知,数量n的区间树最多有4n个结点,因此要设置4n的储存空间。
poj 2182: 有编号1-n的牛,对于每个位置的牛,知道前面编号比它小的有多少头,求编号的顺序。
给出小的个数的数组c[n] ,通过列举可以发现a[n]=c[n]+1。a[n]是剩下的编号中第几大的数,通过这个规律用暴力查找可以很容易的做出来
for(i=n;i>=1;i--)
{ k=0;
for(j=1;j<=n;j++)
{ if(num[j]!=-1) k++; //数组num指的是初始化1.2.3...n的编号
if(k==c[n]+1) { a[i]=num[j]; num[j]=-1; break;} //依次查找,满足条件则记下其值为-1
} }
但是上述代码用到了二重循环,有n平方的复杂度,容易超时,用线段树可以解决这个问题。暴力查找和线段树做法的区别就在于直接查找在每个位置的答案中都要至多执行1-n的循环,而线段树的查找用到了二叉树的查找方法,分线段优化了查找效率。
首先构建二叉树:
void buildtree(int left,int right,int u)
{ tree[u].l=left;
tree[u].r=right;
tree[u].len=right-left+1;
if(left==right) return; //叶结点,不用递归下去了
buildtree(left,(left+right)>>1,u<<1); //递归左子树
buildtree(((left+right)>>1)+1,right,(u<<1)+1); //递归右子树
}
构建好树后就要构建运用二叉树来解题的函数了,根据树和题意可知:如果第c[n]+1大的数大于左子树线段长度,就要转到右子树继续进行查找;如果小于左子树线段长度,继续查找左子树的子树即可
int chazhao(int u,int k)
{ tree[u].len--;
if(tree[u].l==tree[u].r) return tree[u].l; //找到答案
if(tree[u<<1].len>=k) return chazhao(u<<1,k); //不同情况的子树搜索
if(tree[u<<1].len<k) return chazhao((u<<1)+1,k-tree[u<<1].len);
}
完成建树和构建函数后,在合理编写主函数即可,上述使用的是普通二叉树,还可以使用完全二叉树,编程可能更为简便。
2.比赛时二叉树可能会超过存储空间,用离散化的思想可以解决。
离散化思想就是提取、排序并删除相同的端点,将原二叉树压缩,但并没有改变覆盖关系。
二、树状数组
树状数组是一种利用二进制特征进行检索的树状结构。树状数组十分高效并且代码比较简洁。用于修改元素和求和,复杂度缩减到了log2 n。
树状数组核心的是lowbit(x),lowbit(x)=x&-x,功能是找到x的二进制数的最后一个 1。原理是利用负数的补码表示。(书上这里讲的很详细,但我还是没有完全理解)
树状数组有它的模板,还是先套用好了
#define lowbit(x) ((x)&-x)
void add(int x,int d) //更新、修改数组
{ while(x<=n) {
tree[x]+=d;
x+=lowbit(x);}
}
int sum(int x) //求和
{ int sum=0;
while(x>0){
sum+=tree[x];
x-=lowbit(x);}
return sum;}
poj 2182也可以用树状数组实现,它将每一个编号位置的牛都看作是1,即a1=a2=a3…=1,它的和其实就是剩下的编号中第k大的数,因此可以直接用树状数组初始化,不用add函数进行初始化。每一次查找完答案后同样需要更新tree数组代表用过这个值.
for(int i=1;i<=n;i++)
{ tree[i]=lowbit(i);}
for(int i=n;i>=0;i--)
{ int x=findpos(pre[i]+1);
add(x,-1);
ans[i]=x;}
前面套用模板即可,用add更新树状数组的值,用sum计算比它大的有几个数。来构建查找答案的函数,查找答案运用二分查找法的思想,逐渐缩小区间最后得到答案。
int findpos(int x){
int l=1;r=n;
while(l<r){
int mid=(l+r)>>1;
if(sum[mid]<x)
l=mid+1;
else r=mid;
} return l;
}
同一道题可以运用线段树和树状数组两种方法,可以看出线段树主要难度在于构建二叉树和根据题目编写查找函数,而树状数组直接套用了模板后只编写了一个简便的二分查找函数。树状数组的优点在于代码简洁,便于思考;而线段树的优点在于应用广泛,和题目的重合点高。