线段树算法讲解
问:给你一个长度为n的数组,然后询问m次,每次询问是给定两个数x,y。求在【x,y】区间的数之和。
也许你会说遍历,但若n为1e6,m为1e3呢?这是可以用一种数据结构——线段树。
线段树的每一个点维护一个[l,r)的区间。长这样:
图中,数组长度为6,第一个节点维护的是【1,6】的最大值,左节点维护的是【1,3】的最大值,以此类推,直至叶子节点。
下面,以求区间和为例来进一步说明。
定义:
const int maxn = 1e5 + 10;
struct node{
int l, r;//左右节点
int data;//存储的值
int mid()
{
return (l + r) >> 1;
}
}sum[maxn << 2];//一般开四倍空间
线段树的构建
void build(int root, int l, int r)
{
sum[root].l = l, sum[root].r = r;//给每个节点所维护的区间赋值
if(sum[root].l == sum[root].r)//到达叶子节点
{
sum[root].data = a[sum[root].l];
return;
}
int mid = sum[root].mid();
build(root << 1, l, mid);//左右递归建树
build(root << 1 | 1, mid + 1, r);
push_up(root);//精髓所在
}
观察代码,可以发现,线段树的构建是不断地递归直至叶子节点赋值。叶子节点赋值之后用push_up函数为其父亲节点赋值。
push_up函数
void push_up(int root)
{
sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}
单点修改
void update(int root, int p, int v)//p位置的值加v
{
if(sum[root].l == sum[root].r)//找到需要修改的节点
{
sum[root].data += v;
return;
}
int mid = sum[root].mid();
if(p <= mid)//若在左子节点内
update(root << 1, p, v);
else if(p > mid)//若在右子节点内
update(root << 1 | 1, p, v);
push_up(root);//更新父节点
}
单点修改情况下的查询
int query(int root, int l, int r)
{
int ans = 0;
if(sum[root].l >= l && sum[root].r <= r)
{
return sum[root].data;
}
int mid = sum[root].mid();
if(mid >= l)//若所查询区间和左子节点区间有交集
ans += query(root << 1, l, r);
if(mid + 1 <= r)//若和右子节点区间有交集
ans += query(root << 1 | 1, l, r);
return ans;
}
上面是单点修改的情况,下面讲解区间修改
在对某个全进修改时需要用到lazy标记。当使用lzay标记某个区间时,此区间之上的区间显示的内容是修改过的,但其子区间没有被修改,当需要用到子区间时会通过push_down函数进行修改,这样做主要是为了减少时间复杂度。
void update(int root, int l, int r, int v)//[l, r]区间的值更改为v
{
if(sum[root].l >= l && sum[root].r <= r)//当前节点区间完全子啊需修改区间内
{
sum[root].data = v * (sum[root].r - sum[root].l + 1);
lazy[root] = v;
return;
}
push_down(root);//查询是否有lazy标记
int mid = sum[root].mid();
if(mid >= l)//与做区间有交集
update(root << 1, l, r, v);
if(mid + 1 <= r)//与有区间有交集
update(root << 1 | 1, l, r, v);
push_up(root);
}
push_down函数
void push_down(int root)
{
if(lazy[root])
{
//lazy标记下移
lazy[root << 1] = lazy[root];
lazy[root << 1 | 1] = lazy[root];
//更新左右子节点区间
sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
lazy[root] = 0;//已经下移,归零
}
}
有lazy标记的查询区间
int query(int root, int l, int r)//查询[l, r] 区间
{
int ans = 0;
if(sum[root].l >= l && sum[root].r <= r)
{
return sum[root].data;
}
push_down(root);//查询lazy标记
int mid = sum[root].mid();
if(mid >= l)
ans += query(root << 1, l, r);
if(mid + 1 <= r)
ans += query(root << 1 | 1, l, r);
return ans;
}
附上完整的单点修改和区间修改代码:
单点修改
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;
const int maxn = 1e5 + 10;
struct node{
int l, r;//左右节点
int data;//存储的值
int mid()
{
return (l + r) >> 1;
}
}sum[maxn << 2];//一般开四倍空间
int a[maxn];
int n;
char str[10];
void init()
{
mem(a, 0);
mem(sum, 0);
}
void push_up(int root)
{
sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}
void build(int root, int l, int r)
{
sum[root].l = l, sum[root].r = r;//给每个节点所维护的区间赋值
if(sum[root].l == sum[root].r)//到达叶子节点
{
sum[root].data = a[sum[root].l];
return;
}
int mid = sum[root].mid();
build(root << 1, l, mid);//左右递归建树
build(root << 1 | 1, mid + 1, r);
push_up(root);//精髓所在
}
void update(int root, int p, int v)//p位置的值加v
{
if(sum[root].l == sum[root].r)//找到需要修改的节点
{
sum[root].data += v;
return;
}
int mid = sum[root].mid();
if(p <= mid)//若在左子节点内
update(root << 1, p, v);
else if(p > mid)//若在右子节点内
update(root << 1 | 1, p, v);
push_up(root);//更新父节点
}
int query(int root, int l, int r)
{
int ans = 0;
if(sum[root].l >= l && sum[root].r <= r)
{
return sum[root].data;
}
int mid = sum[root].mid();
if(mid >= l)//若所查询区间和左子节点区间有交集
ans += query(root << 1, l, r);
if(mid + 1 <= r)//若和右子节点区间有交集
ans += query(root << 1 | 1, l, r);
// if(mid >= r)
// ans += query(root << 1, l, r);
// else if(mid < l)
// ans += query(root << 1 | 1, l, r);
// else
// {
//// ans += query(root << 1, l, mid);
//// ans += query(root << 1 | 1, mid + 1, r);
// ans += query(root << 1, l, r);
// ans += query(root << 1 | 1, l, r);
// }
return ans;
}
int main()
{
int t;
scanf("%d", &t);
int num = 1;
while(t--)
{
init();
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
build(1, 1, n);
// for(int i = 1; i <= n; i++)
// {
// cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
// }
printf("Case %d:\n", num++);
while(scanf("%s", str))
{
// cout << str << endl;
// for(int i = 1; i <= n; i++)
// {
// cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
// }
// cout << endl;
if(str[0] == 'E') break;
int x, y;
scanf("%d%d", &x, &y);
if(str[0] == 'A')
{
update(1, x, y);
}
else if(str[0] == 'S')
update(1, x, -y);
else if(str[0] == 'Q')
// cout << query(1, x, y) << endl;
printf("%d\n", query(1, x, y));
}
}
return 0;
}
区间修改
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;
const int maxn = 5e5;
struct node{
int l, r;
int data;
int mid()
{
return (l + r) >> 1;
}
}sum[maxn << 2];
//int a[maxn];
int lazy[maxn];
int n;
//char str[10];
void init()
{
// mem(a, 0);
mem(sum, 0);
mem(lazy, 0);
}
void push_up(int root)
{
sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}
void build(int root, int l, int r)
{
sum[root].l = l, sum[root].r = r;
if(sum[root].l == sum[root].r)
{
// sum[root].data = a[sum[root].l];
sum[root].data = 1;
return;
}
int mid = sum[root].mid();
build(root << 1, l, mid);
build(root << 1 | 1, mid + 1, r);
push_up(root);
}
//void update(int root, int p, int v)
//{
// if(sum[root].l == sum[root].r)
// {
// sum[root].data += v;
// return;
// }
// int mid = sum[root].mid();
// if(p <= mid)
// update(root << 1, p, v);
// else if(p > mid)
// update(root << 1 | 1, p, v);
// push_up(root);
//}
void push_down(int root)
{
if(lazy[root])
{
//lazy标记下移
lazy[root << 1] = lazy[root];
lazy[root << 1 | 1] = lazy[root];
//更新左右子节点区间
sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
lazy[root] = 0;//已经下移,归零
}
}
void update(int root, int l, int r, int v)//[l, r]区间的值更改为v
{
if(sum[root].l >= l && sum[root].r <= r)//当前节点区间完全子啊需修改区间内
{
sum[root].data = v * (sum[root].r - sum[root].l + 1);
lazy[root] = v;
return;
}
push_down(root);//查询是否有lazy标记
int mid = sum[root].mid();
if(mid >= l)//与做区间有交集
update(root << 1, l, r, v);
if(mid + 1 <= r)//与有区间有交集
update(root << 1 | 1, l, r, v);
push_up(root);
}
int query(int root, int l, int r)//查询[l, r] 区间
{
int ans = 0;
if(sum[root].l >= l && sum[root].r <= r)
{
return sum[root].data;
}
push_down(root);//查询lazy标记
int mid = sum[root].mid();
if(mid >= l)
ans += query(root << 1, l, r);
if(mid + 1 <= r)
ans += query(root << 1 | 1, l, r);
// if(mid >= r)
// ans += query(root << 1, l, r);
// else if(mid < l)
// ans += query(root << 1 | 1, l, r);
// else
// {
//// ans += query(root << 1, l, mid);
//// ans += query(root << 1 | 1, mid + 1, r);
// ans += query(root << 1, l, r);
// ans += query(root << 1 | 1, l, r);
// }
return ans;
}
int main()
{
int t;
scanf("%d", &t);
int num = 1;
while(t--)
{
init();
scanf("%d", &n);
int m;
// for(int i = 1; i <= n; i++)
// scanf("%d", &a[i]);
build(1, 1, n);
// for(int i = 1; i <= n; i++)
// {
// cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
// }
scanf("%d", &m);
for(int i = 0; i < m; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
update(1, x, y, z);
}
printf("Case %d: The total value of the hook is %d.\n", num++, sum[1].data);
// cout << sum[1].data << endl;
}
return 0;
}
OK,上课去了,,应该还来得及。。逃