概念
原理
线段树是分治法和二叉树的结合,二叉树上的节点都是根据分治得到的。节点所表示的,也就是线段,可以是区间和、最值或者是其他的,,每次分治,左右子树各一半,每个节点的值代表了以它为根的子树上所有节点的值。通过线段树,大区间的解可以从小区间的解合并而来。
构造
令节点编号为p,指向区间[pl,pr],以求区间和为例(换成求最值或者其他也没有问题)。
代码:
struct t{
int L;
int R;
int data;
}tree[N*4];
void build(int p,int pl,int pr) {
tree[p].L = pl;
tree[p].R = pr;
if(pl == pr) {
tree[p].data = input[pl];
return;
}
int mid = (pl + pr)>>1;//分治
build(p * 2,pl,mid);//左子树
build(p * 2 + 1,mid + 1,pr);//右子树
tree[p].data = tree[p * 2].data + tree[p * 2 + 1].data;
}
区间查询
不论是区间和还是最值或者其他,都可以通过线段树进行查询。比如一个一棵包含10个元素的线段树,当需要查询任意区间[L,R]的最小值时,假设寻找的是[2,5]的最小值,递归查询区间[2,2]、[3,3]、[4,5],就可以得到三个区间各自的最小值,然后再对它们求最小值,求和也是一样的道理。查询区间[L,R]的和,当查询到某个递归节点p时,会有两种情况:①[L,R]完全覆盖了[pl,pr],此时直接返回p的data值即可。②[L,R]与[pl,pr]部分重叠,分别搜索左右子节点。
代码:
int query(int L,int R,int p,int pl,int pr) {
//完全覆盖
if(L <= pl && R >= pr) {
return tree[p].data;
}
//部分覆盖
int mid = (pl + pr)>>1;
int ans = 0;
if(L <= mid) {
ans += query(L,R,p * 2,pl,mid);
}
if(R > mid) {
ans += query(L,R,p * 2 + 1,mid + 1,pr);
}
return ans;
}
例题
题目:
For the daily milking, Farmer John's N cows (1 ≤ N ≤ 100,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.
Farmer John has made a list of Q (1 ≤ Q ≤ 30) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.
输入:
Line 1: Two space-separated integers, N and Q.
Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2..N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.
输出:
Lines 1..Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.
示例1:
输入:
6 3 1 7 3 4 2 5 1 5 4 6 2 2
输出:
6 3 0
思路:
本题其实也就是用到了上述线段树的思想,找出最大值与最小值进行求差。
代码:
#include<bits/stdc++.h>
using namespace std;
int tree1[400005];//最大值
int tree2[400005];//最小值
void build(int p,int pl,int pr)
{
if(pl == pr)
{
cin >> tree1[p];
tree2[p] = tree1[p];
return;
}
int mid = (pl + pr) >> 1;
build(p<<1,pl,mid);
build(p<<1|1,mid + 1,pr);
tree1[p] = max(tree1[p<<1],tree1[p<<1|1]);
tree2[p] = min(tree2[p<<1],tree2[p<<1|1]);
}
int querymax(int L,int R,int p,int pl,int pr)//查询最大值
{
if(L <= pl && R >= pr) {
return tree1[p];
}
int mid = (pl + pr) >> 1;
int ans=0;
if(L <= mid) {
ans=max(ans,querymax(L,R,p<<1,pl,mid));
}
if(R > mid) {
ans=max(ans,querymax(L,R,p<<1|1,mid + 1,pr));
}
return ans;
}
int querymin(int L,int R,int p,int pl,int pr)//查询最小值
{
if(L <= pl && R >= pr) {
return tree2[p];
}
int mid = (pl + pr) >> 1;
int ans=1000001;
if(L <= mid) {
ans=min(ans,querymin(L,R,p<<1,pl,mid));
}
if(R > mid) {
ans=min(ans,querymin(L,R,p<<1|1,mid + 1,pr));
}
return ans;
}
int main()
{
int n,m,s,e;
cin >> n >> m;
build(1,1,n);
for(int i = 1; i <= m; i++)
{
cin >> s >> e;
cout << querymax(s,e,1,1,n)-querymin(s,e,1,1,n) << endl;
}
return 0;
}