还是那句话, 知其然不知其所以然真的是一件容易引发伤痛的事情。
所以忍不住来一发线段树总结。。
因为当时学线段树的时候也就是刚学了几个月编程, 所以一直觉得它挺长的。
但现在再看一看实在是简洁易懂的, 一个线段树也就三十行而且思路非常的清晰写起来非常的快。
以前对于线段树的lazy思想一直没有系统地想过, 都是在什么时候遇到一道题然后自己YY出来的, 经常调不对, 但事实上lazy思想做的就是不把那些没有必要的细枝末节更新。 比如最后一个更新是把整个区间更新后询问整个区间维护的值, 如果单点修改的话就要把整棵树都修改了然后再询问, 但是这时的询问实际上只用到了tree[1]这一个节点的值, 所以那些更新都是没有意义的。 而我们要改进的就是把作用在一个点上的所有更新都合并起来, 然后等到需要用到这个点的时候再给它更新, 这样理论上的时间复杂度就还是nlogn 的。
注意它维护的那个改变的值 是代表着当前这个点已经更新过了, 但是它的子节点还没有更新过, 所以当访问一个点的子节点的时候先查询这个子节点需不需要改变, 改变了以后再访问这个子节点。
裸题 hdu1698, 做了好几遍了,,,但是这一遍做的自我感觉是最优美的! 四十行
#include <iostream>
#include <cstdio>
#define MAXN 100005
using namespace std;
int t, n, q;
struct Tree{
int l, r, num, c;
}tree[MAXN * 4];
void build(int t, int l, int r){
tree[t].l = l; tree[t].r = r; tree[t].num = r - l + 1; tree[t].c = 0;
if(l == r)return;
int mid = (l + r)>>1;
build(t + t, l, mid); build(t + t + 1, mid + 1, r);
}
void update(int l, int r, int z, int t){
int tl = tree[t].l, tr = tree[t].r; int mid = (tl + tr)>>1;
if(tl >= l && tr <= r){
tree[t].c = z;
tree[t].num = (tr - tl + 1) * z; return ;
}
if(tree[t].c){ int cc = tree[t].c; tree[t].c = 0; //down
tree[t + t].c = tree[t + t + 1].c = cc;
tree[t + t + 1].num = cc * (tree[t + t + 1].r - tree[t + t + 1].l + 1);
tree[t + t].num = cc * (tree[t + t].r - tree[t + t].l + 1);
}
if(l <= mid)update(l, r, z, t + t);
if(r >= mid + 1)update(l, r, z, t + t + 1);
tree[t].num = tree[t + t].num + tree[t + t + 1].num; //up
}
int main()
{
scanf("%d", &t); for(int cas = 1; cas <= t; cas ++){
scanf("%d%d", &n, &q);
build(1, 1, n);
while(q --){
int x, y, z; scanf("%d%d%d", &x, &y, &z);
update(x, y, z, 1);
} printf("Case %d: The total value of the hook is %d.\n", cas, tree[1].num);
}
return 0;
}
另外值得一提的是看到这道题有人打了这样的暴力:
#include <iostream>
#include <cstdio>
using
namespace
std;
int
data[100005][3];
int
main()
{
int
t,q,n,i,j,sum,k,v;
scanf
(
"%d"
,&t);
for
(i=1;i<=t;i++)
{
scanf
(
"%d%d"
,&n,&q);
for
(j=1;j<=q;j++)
scanf
(
"%d%d%d"
,&data[j][0],&data[j][1],&data[j][2]);
sum=0;
for
(k=1;k<=n;k++)
{
v=1;
for
(j=q;j>=1;j--)
if
(data[j][0]<=k && k<=data[j][1])
//寻找k所在的更新区间,若存在则更新,不存在v=1不变
{
v=data[j][2];
//若找的最后面的更新区间,则停止,因为前面的会被覆盖
break
;
}
sum+=v;
}
printf
(
"Case %d: The total value of the hook is %d.\n"
,i,sum);
}
return
0;
}
poj 3468
还是最裸最裸的线段树, 不过我最开始还是调了好久。。。。。果然弱得不能忍。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define MAXN 100005
using namespace std;
int a[MAXN], n, q;
struct Tree{
int l, r; long long delta, sum;
}tree[MAXN * 4];
void build(int t, int l, int r){
tree[t].l = l; tree[t].r = r; tree[t].delta = 0;
if(l == r){tree[t].sum = (long long)a[l]; return;}
int mid = (l + r)>>1;
build(t + t, l, mid); build(t + t + 1, mid + 1, r);
tree[t].sum = tree[t + t].sum + tree[t + t + 1].sum;
}
inline void pushdown(int t){
if(tree[t].delta){ long long dd = tree[t].delta; tree[t].delta = 0;
tree[t + t].delta += dd; tree[t + t + 1].delta += dd;
tree[t + t].sum += dd * (tree[t + t].r - tree[t + t].l + 1);
tree[t + t + 1].sum += dd * (tree[t + t + 1].r - tree[t + t + 1].l + 1);}
}
void update(int t, int l, int r, int z){
if(tree[t].l >= l && tree[t].r <= r){
tree[t].delta += z;
tree[t].sum += (tree[t].r - tree[t].l + 1) * (long long)z; return;
} pushdown(t);
int mid = (tree[t].l + tree[t].r)>>1;
if(mid >= l)update(t + t, l, r, z);
if(mid + 1 <= r)update(t + t + 1, l, r, z);
tree[t].sum = tree[t + t].sum + tree[t + t + 1].sum;
}
long long ask(int t, int l, int r){
if(tree[t].l >= l && tree[t].r <= r)return tree[t].sum;
pushdown(t);
int mid = (tree[t].l + tree[t].r)>>1;
long long ret = 0;
if(mid >= l) ret += ask(t + t, l, r);
if(mid + 1 <= r) ret += ask(t + t + 1, l, r); return ret;
}
int main()
{
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i ++)scanf("%d", &a[i]);
build(1, 1, n);
for(int i = 1; i <= q; i ++){ getchar();
char c; int x, y; scanf("%c%d%d", &c, &x, &y);
if(c == 'Q'){printf("%I64d\n", ask(1, x, y));}
else {int z; scanf("%d", &z); update(1, x, y, z);}
}
// system("pause");
return 0;
}
还有一点就是只看自己的代码的时候不觉得有什么, 但在同一道题下, 看一下别人的代码就会感慨自己写得有多丑 了!!
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define LL long long
const int maxn = 111111;
LL add[maxn<<2];
LL sum[maxn<<2];
void PushUp(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void PushDown(int rt,int m) {
if (add[rt]) {
add[rt<<1] += add[rt];
add[rt<<1|1] += add[rt];
sum[rt<<1] += add[rt] * (m - (m >> 1));
sum[rt<<1|1] += add[rt] * (m >> 1);
add[rt] = 0;
}
}
void build(int l,int r,int rt) {
add[rt] = 0;
if (l == r) {
scanf("%lld",&sum[rt]);
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
add[rt] += c;
sum[rt] += (LL)c * (r - l + 1);
return ;
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
PushUp(rt);
}
LL query(int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
LL ret = 0;
if (L <= m) ret += query(L , R , lson);
if (m < R) ret += query(L , R , rson);
return ret;
}
int main() {
int N , Q;
scanf("%d%d",&N,&Q);
build(1 , N , 1);
while (Q --) {
char op[2];
int a , b , c;
scanf("%s",op);
if (op[0] == 'Q') {
scanf("%d%d",&a,&b);
printf("%lld\n",query(a , b , 1 , N , 1));
} else {
scanf("%d%d%d",&a,&b,&c);
update(a , b , c , 1 , N , 1);
}
}system("pause");
return 0;
}
这个代码也就比我的那个多了20行, 但是真的漂亮到不知道哪里去了。。。不过他用的是那种不开struct的线段树, 所以说变量名就都会显得比较短, 其实我这种线段树应该也可以define一下然后看起来就会漂亮多了!
改了一会改不下去了。。但大概是这个效果:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define MAXN 100005
#define ll long long
#define S(x) tree[x].sum
#define R(x) tree[x].r
#define L(x) tree[x].l
#define D(x) tree[x].delta
#define ls(x) x<<1
#define rs(x) (x<<1)+1
using namespace std;
int a[MAXN], n, q;
struct Tree{
int l, r;
long long delta, sum;
}tree[MAXN * 4];
void build(int t, int l, int r){
L(t) = l;
R(t) = r;
D(t) = 0;
if(l == r){
S(t) = (ll)a[l];
return;
}
int mid = (l + r)>>1;
build(ls(x), l, mid);
build(rs(x), mid + 1, r);
S(t) = S(ls(x)) + S(rs(x));
}
inline void pushdown(int t){
if(D(t)){
ll dd = D(t);
D(t) = 0;
D(ls(t)) += dd;
D(rs(t)) += dd;
S(ls(t)) += dd * (R(ls(t)) - L(ls(t)) + 1);
S(rs(t)) += dd * (R(rs(t)) - L(rs(t)) + 1);
}
}
void update(int t, int l, int r, int z){
if(L(t) >= l && R(t) <= r){
D(t) += z;
S(t) += (R(t) - L(t) + 1) * (ll)z;
return;
}
pushdown(t);
int mid = (L(t) + R(t))>>1;
if(mid >= l)update(ls(t), l, r, z);
if(mid + 1 <= r)update(rs(t), l, r, z);
S(t) = S(ls(t)) + S(rs(t));
}
风格很怪异啊。。。
还有这道题, 我还没写呢, 但是感觉还是有价值看一看的, 一下是一个人的题解
o poj3225 Help with Intervals
题意:区间操作,交,并,补等
思路:
我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 ,且[l,r]区间0/1互换
S:[l,r]区间0/1互换
成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作
很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了
所以当一个节点得到覆盖标记时把异或标记清空
而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记
开区间闭区间只要数字乘以2就可以处理(偶数表示端点,奇数表示两端点间的区间)
线段树功能:update:成段替换,区间异或 query:简单hash
?View Code CPP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #include <cstdio> #include <cstring> #include <cctype> #include <algorithm> using namespace std; #define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1
const int maxn=131072; bool hash[maxn]; int cover[maxn<<2]; int XOR[maxn<<2]; void FXOR(int rt){ if (cover[rt]!=-1) cover[rt]^=1; else XOR[rt]^=1; } void PushDown(int rt){ if (cover[rt]!=-1){ cover[rt<<1]= cover[rt<<1|1]= cover[rt]; XOR[rt<<1]= XOR[rt<<1|1]=0; cover[rt]=-1; } if (XOR[rt]){ FXOR(rt<<1); FXOR(rt<<1|1); XOR[rt]=0; } } void update(char op,int L,int R,int l,int r,int rt) { if (L <= l && r<= R){ if (op == 'U'){ cover[rt]=1; XOR[rt]=0; } else if (op=='D'){ cover[rt]=0; XOR[rt]=0; } else if (op=='C' || op == 'S'){ FXOR(rt); } return ; } PushDown(rt); int m = (l + r)>>1; if (L <= m) update(op , L , R , lson); else if (op =='I'|| op == 'C') { XOR[rt<<1]= cover[rt<<1]=0; } if (m < R) update(op , L , R , rson); else if (op =='I'|| op == 'C') { XOR[rt<<1|1]= cover[rt<<1|1]=0; } } void query(int l,int r,int rt){ if (cover[rt]==1){ for (int it = l; it<= r ; it ++){ hash[it]=true; } return ; } else if (cover[rt]==0)return; if (l == r) return ; PushDown(rt); int m = (l + r)>>1; query(lson); query(rson); } int main(){ cover[1]= XOR[1]=0; char op , l , r; int a , b; while ( ~scanf("%c %c%d,%d%c\n",&op ,&l , &a , &b ,&r)){ a <<= 1 , b <<= 1; if (l == '(') a++; if (r == ')') b--; if (a > b) { if (op == 'C' || op == 'I'){ cover[1]= XOR[1]=0; } } else update(op , a , b , 0 , maxn , 1); } query(0 , maxn ,1); bool flag = false; int s = -1 , e; for (int i = 0 ; i <= maxn; i++){ if (hash[i]){ if (s == -1) s= i; e = i; } else { if (s != -1) { if(flag)printf(" "); flag = true; printf("%c%d,%d%c",s&1?'(':'[' , s>>1 , (e+1)>>1 , e&1?')':']'); s =-1; } } } if (!flag)printf("empty set"); puts(""); return 0; } |
然后就是区间合并的啦!
· 区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并
就是像小白逛公园那样的题了, 话说小白逛公园是我A的第一道线段树的题。。。深感被cy欺骗了。。。她应该先让我做单点修改的题啊,还记得当时我做那题做得很困难。
poj3667
今天poj上不去是什么情况。。T T, 一会再交
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <time.h>
#include <queue>
#define MAXN 50005
using namespace std;
struct Tree{
int lmax, rmax, mmax, l, r, c;
}tree[MAXN * 4];
inline void pushdown(int x){
if(tree[x].c != -1){
tree[x<<1].c = tree[x<<1|1].c = tree[x].c;
tree[x<<1].lmax = tree[x<<1].rmax = tree[x<<1].mmax = tree[x].c * (tree[x<<1].r - tree[x<<1].l + 1);
tree[x<<1|1].lmax = tree[x<<1|1].rmax = tree[x<<1|1].mmax = tree[x].c * (tree[x<<1|1].r - tree[x<<1|1].l + 1);
tree[x].c = -1;
}
}
inline void pushup(int t){
tree[t].lmax = tree[t<<1].lmax;
tree[t].rmax = tree[t<<1|1].rmax;
if(tree[t<<1].lmax == tree[t<<1].r - tree[t<<1].l + 1) tree[t].lmax += tree[t<<1|1].lmax;
if(tree[t<<1|1].rmax == tree[t<<1|1].r - tree[t<<1|1].l + 1) tree[t].rmax += tree[t<<1].rmax;
tree[t].mmax = max(max(tree[t<<1].mmax, tree[t<<1|1].mmax), tree[t<<1].rmax + tree[t<<1|1].lmax);
}
void build(int t, int l, int r){
tree[t].lmax = tree[t].rmax = tree[t].mmax = r - l + 1;
tree[t].l = l; tree[t].r = r; tree[t].c = -1;
if(l == r)return;
int mid = l + r>>1;
build(t<<1, l, mid); build(t<<1|1, mid + 1, r);
}
int ask(int t, int w){
int l = tree[t].l, r = tree[t].r;
if(l == r)return l;
pushdown(t);
int mid = l + r >>1;
if(tree[t<<1].mmax >= w)return ask(t<<1, w);
if(tree[t<<1].rmax + tree[t<<1|1].lmax >= w) return mid - tree[t<<1].rmax + 1;
return ask(t<<1|1, w);
}
void update(int l, int r, int t, int c){
if(tree[t].l >= l && tree[t].r <= r){
tree[t].lmax = tree[t].rmax = tree[t].mmax = c * (tree[t].r - tree[t].l + 1);
tree[t].c = c; return;
}
pushdown(t);
int mid = (tree[t].l + tree[t].r)>>1;
if(l <= mid)update(l, r, t<<1, c);
if(r >= mid + 1)update(l, r, t<<1|1, c);
pushup(t);
}
int main()
{
int n, m; scanf("%d%d", &n, &m);
build(1, 1, n);
while(m --){
int z, a, b; scanf("%d%d", &z, &a);
if(z == 1){
if(tree[1].mmax < a)puts("0");
else{
int p = ask(1, a);
printf("%d\n", p);
update(p, p + a - 1, 1, 0);
}
}
else{
scanf("%d", &b);
update(a, a + b - 1, 1, 1);
}
}//system("pause");
return 0;
}
hdu3308
同区间合并裸题,,但是调了好久,,这两天一直在调代码有点疲惫了呜呜呜~~~~(>_<)~~~~
很快打完的代码总是调不出来,,还是做的题太少了,,加油吧一直敲代码我的代码能力一定会上去的! >_<
扫描线
这算是线段树的一个经典应用吧,,前几天做题还遇到了,,当时好像现场没调出来~~~~(>_<)~~~~
poj 1177
所有线段树的总结中都有这个!
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define MAXN 50010
using namespace std;
int n;
int x[2 * MAXN], end;
struct node{
int l, r;
int len, lenall;
int num, seg;
bool lcover, rcover;
}tree[MAXN * 8];
struct line{
int s, e;
int y;
bool down;
}xline[2 * MAXN];
void build(int l, int r, int t){
tree[t].l = l;
tree[t].r = r;
tree[t].len = x[r] - x[l];
tree[t].lenall = tree[t].num = tree[t].seg = 0;
tree[t].lcover = tree[t].rcover = 0;
if(r - l > 1){
int mid = (l + r)>>1;
build(l, mid, t + t),
build(mid, r, t + t + 1);
}
}
void update(int r){
if(tree[r].num > 0){
tree[r].lenall = tree[r].len;
tree[r].seg = 1;
tree[r].lcover = tree[r].rcover = 1;
}
else if(tree[r].r - tree[r].l > 1){
tree[r].lenall = tree[2 * r].lenall + tree[2 * r + 1].lenall;
tree[r].lcover = tree[2 * r].lcover;
tree[r].rcover = tree[2 * r + 1].rcover;
tree[r].seg = tree[2 * r].seg + tree[2 * r + 1].seg
- tree[2 * r].rcover * tree[2 * r + 1].lcover;
}
else{
tree[r].lenall = tree[r].seg = 0;
tree[r].lcover = tree[r].rcover = 0;
}
}
void insert(int l, int r, int t){
if(l == tree[t].l && r == tree[t].r)
tree[t].num ++;
else {
int mid = (tree[t].l + tree[t].r) >> 1;
if(r <= mid) insert(l, r, 2 * t);
else if(l >= mid) insert(l, r, 2 * t + 1);
else insert(l, mid, 2 * t), insert(mid, r, 2 * t + 1);
}
update(t);
}
void deletee(int l, int r, int t){
if(l == tree[t].l && r == tree[t].r)
tree[t].num --;
else {
int mid = (tree[t].l + tree[t].r)>>1;
if(r <= mid) deletee(l, r, 2 * t);
else if(l >= mid) deletee(l, r, 2 * t + 1);
else deletee(l, mid, 2 * t), deletee(mid, r, 2 * t + 1);
}
update(t);
}
int find(int key){
return lower_bound(x + 1, x + end + 1, key) - x;
}
bool cmp(const line &l1, const line &l2){
return l1.y < l2.y;
}
int main()
{
scanf("%d", &n);
int x1, x2, y1, y2;
end = 1;
for(int i = 0; i < n; i ++){
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x[end ++] = x1;
x[end ++] = x2;
xline[2 * i].s = xline[2 * i + 1].s = x1;
xline[2 * i].e = xline[2 * i + 1].e = x2;
xline[2 * i].y = y1; xline[2 * i + 1].y = y2;
xline[2 * i].down = 1; xline[2 * i + 1].down = 0;
}
sort(x + 1, x + end) ;
end = unique(x + 1, x + end) - (x + 1);
build (1, end, 1);
int ans = 0, pre = 0;
sort(xline, xline + 2 * n, cmp);
for(int i = 0; i < 2 * n - 1; i ++){
if(xline[i].down){
insert(find(xline[i].s), find(xline[i].e), 1);
}
else
deletee(find(xline[i].s), find(xline[i].e), 1);
ans += tree[1].seg * (xline[i + 1].y - xline[i].y) * 2;
ans += abs(tree[1].lenall - pre);
pre = tree[1].lenall;
}
deletee(find(xline[2 * n - 1].s), find(xline[2 * n - 1].e), 1);
ans += abs(tree[1].lenall - pre);
printf("%d\n",ans);
// system("pause");
return 0;
}