这一块关键理解延迟标记(或者说懒惰标记)lz[],就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新或者询问到的时候再更新。

这里主要包括两个方面:

1:成端替换; 2:成端累加(累减);

hdu 1698 ​​http://acm.hdu.edu.cn/showproblem.php?pid=1698​

题意:

给定n个连续的奖牌(每个奖牌都有一个价值),初始都为铜牌。有q个操作X,Y,Z,将区间[x,y]内的奖牌的价值改为Z,问经过q个操作后总的价值为多少。

思路:

就是典型的成段替换,lz标记,关键是注意将lz   pushdown。。


线段树练习——成段更新_#include线段树练习——成段更新_#include_02View Code


#include<iostream>
#include<cstring>
#include <cstdio>
#define maxn 100007
using namespace std;

int val[4*maxn];
int lz[4*maxn];
void pushup(int rt)
{
val[rt] = val[rt<<1] + val[rt<<1|1];
}
void pushdown(int rt,int m)
{
if (lz[rt])
{
lz[rt<<1] = lz[rt<<1|1] = lz[rt];
val[rt<<1] = (m - (m>>1))*lz[rt];//左边是[1,m/2]有m-m/2个
val[rt<<1|1] = (m>>1)*lz[rt];//右边[m/2+1,m]有m/2个
lz[rt] = 0;
}
}

void build(int l,int r,int rt)
{
lz[rt] = 0;
if (l == r)
{
val[rt] = 1;
return ;
}
int m = (l + r)>>1;
build(l,m,rt<<1);
build(m + 1,r,rt<<1|1);
pushup(rt);
}

void update(int L,int R,int sc,int l,int r,int rt)
{
if (l >= L && r <= R)
{
lz[rt] = sc;
val[rt] = lz[rt]*(r - l + 1);
return ;
}
pushdown(rt,r - l + 1);
int m = (l + r)>>1;
if (L <= m) update(L,R,sc,l,m,rt<<1);
if (R > m) update(L,R,sc,m + 1,r,rt<<1|1);
pushup(rt);
}
int main()
{
int t,x,y,z;
int n,q;
int cas = 1;
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
build(1,n,1);
scanf("%d",&q);
while (q--)
{
scanf("%d%d%d",&x,&y,&z);
update(x,y,z,1,n,1);
}
printf("Case %d: The total value of the hook is %d.\n",cas++,val[1]);
}
return 0;
}


 pku 3468 ​​http://poj.org/problem?id=3468​

题意:

给定n个数(n个数比较大,所以要用long long),有两种操作C x,y,z将区间[x,y]的数加z   Q x,y 询问区间[x,y]的总和。

思路:

典型的成端累加减;

注意:在累加减的pushdown里面的书写与成端覆盖里面的不同lz 与 val都是累加减的,因为懒惰标记的原因。


线段树练习——成段更新_#include线段树练习——成段更新_#include_02View Code


#include <cstdio>
#include <cstring>
#include <iostream>
#define maxn 100007
#define ll long long
using namespace std;

ll val[4*maxn];
int lz[4*maxn];

void pushup(int rt)
{
val[rt] = val[rt<<1] + val[rt<<1|1];
}
void pushdown(int rt,int m)
{
if (lz[rt])
{
//都要累加的。
lz[rt<<1] += lz[rt];
lz[rt<<1|1] += lz[rt];
val[rt<<1] += (ll)lz[rt]*(ll)(m - (m>>1));
val[rt<<1|1] += (ll)lz[rt]*(ll)(m>>1);
lz[rt] = 0;
}
}
void build(int l,int r,int rt)
{
lz[rt] = 0;
if (l == r)
{
scanf("%lld",&val[rt]);
return ;
}
int m = (l + r)>>1;
build(l,m,rt<<1);
build(m + 1,r,rt<<1|1);
pushup(rt);
}

void update(int L,int R,int sc,int l,int r,int rt)
{
if (l >= L && r <= R)
{
lz[rt] += sc;//这里也要累加的。
val[rt] += (ll)sc*(ll)(r - l + 1);
return ;
}
pushdown(rt,r - l + 1);
int m = (l + r)>>1;
if (L <= m) update(L,R,sc,l,m,rt<<1);
if (R > m) update(L,R,sc,m + 1,r,rt<<1|1);
pushup(rt);
}

ll query(int L,int R,int l,int r,int rt)
{
if (l >= L && r <= R)
{
return val[rt];
}
pushdown(rt,r - l + 1);
ll res = 0;
int m = (l + r)>>1;
if (L <= m) res += query(L,R,l,m,rt<<1);
if (R > m) res += query(L,R,m + 1,r,rt<<1|1);
return res;
}

int main()
{
int n,m;
char op[2];
int x,y,z;
scanf("%d%d",&n,&m);
build(1,n,1);
while (m--)
{
scanf("%s%d%d",op,&x,&y);
if (op[0] == 'C')
{
scanf("%d",&z);
update(x,y,z,1,n,1);
}
else printf("%lld\n",query(x,y,1,n,1));
}
return 0;
}


 pku 2528 ​​http://poj.org/problem?id=2528​

题意:

给定一个高度确定,长度为10000000的墙,参加海选的人可以将自己的海报贴在上边,海报高度与墙的高度一样,给定每个海报的li,ri(表示这张海报占据li到ri这块空间),海报可以重叠,问最后将n张海报按要求张贴完毕后可看见的海报有多少张?

思路:

题目应该是按墙的长度建树,可是给定的墙的长度太大,直接搞的话会超内存,而给定的n最大取10000也就是说最多我们得到20000个点,所以价格给定的点离散化到1-2*n然后建树,接下来就是经典的成端覆盖,然后就是query访问所以叶子节点,hash统计可见海报的个数。


线段树练习——成段更新_#include线段树练习——成段更新_#include_02View Code


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 10011
using namespace std;

int hash[2*N];
int X[N*2],val[N*8];
int L[N],R[N],ans;
int cmp(int a,int b)
{
return a < b;
}
void pushdown(int rt)
{
if (val[rt])
{
val[rt<<1] = val[rt<<1|1] = val[rt];
val[rt] = 0;
}
}
int bsearch(int l,int r,int sc)
{
while (l <= r)
{
int m = (l + r)>>1;
if (X[m] == sc) return m;
else if (sc < X[m]) r = m -1;
else l = m + 1;
}
return l;
}

void update(int L,int R,int l,int r,int rt,int mk)
{
if (l >= L && r <= R)
{
val[rt] = mk;
return ;
}
pushdown(rt);
int m = (l + r)>>1;
if (L <= m) update(L,R,l,m,rt<<1,mk);
if (R > m) update(L,R,m + 1,r,rt<<1|1,mk);
}

void query(int l,int r,int rt)
{
if (l == r)
{
if (!hash[val[rt]])
{
hash[val[rt]] = 1;
ans++;
}
return;
}
pushdown(rt);
int m = (l + r)>>1;
query(l,m,rt<<1);
query(m + 1,r,rt<<1|1);
}
int main()
{
int t,i,n;
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
/* 这里hash[10000000]搞也可以的
int m = 1;
for (i = 1; i <= n; ++i)
{
scanf("%d%d",&L[i],&R[i]);
if (!hash[L[i]])
{
hash[L[i]] = 1;
X[m++] = L[i];
}
if (!hash[R[i]])
{
hash[R[i]] = 1;
X[m++] = R[i];
}
}
sort(X + 1,X+m,cmp);
m--;
*/
//不过这样时间少,内存好用也少
int mm = 1;
for (i = 1; i <= n; ++i)
{
scanf("%d%d",&L[i],&R[i]);
X[mm++] = L[i];
X[mm++] = R[i];
}

sort(X + 1,X+mm,cmp);
int m = 2;
for (i = 2; i < mm; ++i)
{
if (X[i] != X[i - 1])
X[m++] = X[i];
}
m--;
for (i = 1; i <= n; ++i)
{
int l = bsearch(1,m,L[i]);
int r = bsearch(1,m,R[i]);
update(l,r,1,m,1,i);
}
ans = 0;
for (i = 0; i < 2*N; ++i) hash[i] = 0;
query(1,m,1);
printf("%d\n",ans);
}
}


 

pku 3225 ​​http://poj.org/problem?id=3225​

题意:

给出一系列区间的交,并,补,差,对称差运算,最后求出得到的集合,开始集合为空;

这里主要的难点我认为有三个(这题真心不好写,整死爹了快)

1:首先是区间开与闭的处理,我们在线段树对区间操作时,左右l,r都是闭区间,这里给出的有开区间,而且又不能-1因为(2,3)这样的开区间也不为空,若-1操作无效了。这里的处理方法是所有区间的端点*2,然后处理。

例如:给你(2,3]这样的数据,如何处理呢?

我们将范围乘以2,得到(4,6],然后,如果左边是开区间,则将4加1,得到5,同理,如果右边是开区间,则将6减去一个1。数据乘以2后,得到的结果一定是偶数,而偶数加一减一后,肯定得到奇数。也就是说,如果query一遍之后最后得到的数据是偶数,那就是闭区间,如果得到的数据是奇数,那就对应着开区间。(这样处理太巧妙了YM之);

2:各个操纵对应的线段树操作了;

在数据乘以2转换成成闭区间之后对应的操作

U [a,b] 将区间[a,b]覆盖为1(成段覆盖);

I [a,b] 将区间[0,a - 1] [b +1 ,n] 覆盖成0;

D [a,b] 将区间[a,b]覆盖成0;

C [a,b] 将区间[0,a - 1] [b + 1,n]覆盖成0 区间[a,b] 0换成1 ,1换成0;(就是^1即可)

S [a,b] 将区间[a,b]  0换成1 ,1换成0;(就是^1即可)

3:就是懒惰标记以及抑或标记往下传递以及update时的操作了(我认为这里最难理解了);

lz[]为lazy标记,turn为抑或标记

lz[] == 0 表示该区间全部覆盖为0, 1表示全部覆盖为1,-1表示该区间无向下传递的操作(其左右子树可能有区间为0的区间,也可能有区间为1的区间)。

turn[] = 1 表示有抑或操作,0 表示无抑或操作

当update接受 0 ,1信息即全部覆盖为0,1时直接不用考虑原来的操作,直接覆盖就可以,而接受3抑或操作时,原来的lz 0 ,1标记直接抑或就可以。而对于lz = -1的其左右孩子可能存在整个区间为0和1的,所以要进行抑或的累积。

往下传递时首先考虑lz的传递操作,因为经过上面一些列的操作如果该区存在间既有lz操作又有turn操作时turn一定在lz之后(因为如果turn在lz之前的话,那么后来的lz直接将其覆盖了,也就无turn操作了)。

ps:我在这里处理的时候数组大小开错了,导致调了很长时间,注意数组的大小。


线段树练习——成段更新_#include线段树练习——成段更新_#include_02View Code


#include <cstdio>
#include <cstring>
#include <iostream>
#define maxn 65537
using namespace std;

int lz[8*maxn],turn[8*maxn];
bool hash[2*maxn];

void pushdown(int rt)
{
if (lz[rt] != -1)
{
lz[rt<<1] = lz[rt<<1|1] = lz[rt];
turn[rt<<1] = turn[rt<<1|1] = turn[rt];
lz[rt] = -1;
turn[rt] = 0;
}
if (turn[rt])
{
if (lz[rt<<1] != -1) lz[rt<<1] ^= 1;
else turn[rt<<1] ^= 1;
if (lz[rt<<1|1] != -1) lz[rt<<1|1] ^= 1;
else turn[rt<<1|1] ^= 1;
turn[rt] = 0;
}
}
void build(int l,int r,int rt)
{
lz[rt] = 0;
turn[rt] = 0;
if (l == r) return ;
int m = (l + r)>>1;
build(l,m,rt<<1);
build(m + 1,r,rt<<1|1);
}

void update(int L,int R,int mk,int l,int r,int rt)
{
if (l >= L && r <= R)
{
if (mk == 3)//抑或标记
{
if (lz[rt] != -1) lz[rt] ^= 1;//如果原来有覆盖标记
else turn[rt] ^= 1;//如果原来没有覆盖标记
}
//入如果是覆盖标记直接覆盖
else
{
lz[rt] = mk;
turn[rt] = 0;
}
return ;
}
pushdown(rt);
int m = (l + r)>>1;
if (L <= m) update(L,R,mk,l,m,rt<<1);
if (R > m) update(L,R,mk,m + 1,r,rt<<1|1);
}
void query(int l,int r,int rt)
{
if (l == r)
{
if (lz[rt] == 1 && !hash[l])
{
hash[l] = true;
}
return ;
}
int m = (l + r)>>1;
pushdown(rt);
query(l,m,rt<<1);
query(m + 1,r,rt<<1|1);
}
int main()
{
//freopen("1.txt","r",stdin);
int a,b;
char op[2],li,ri;
int n = 2*maxn;
build(0,n,1);

while (~scanf("%s %c%d,%d%c",op,&li,&a,&b,&ri))
{
a <<= 1; b <<= 1;
if (li == '(') a++;
if (ri == ')') b--;

if (op[0] =='U') update(a,b,1,0,n,1);
if (op[0] == 'D') update(a,b,0,0,n,1);
if (op[0] == 'I' || op[0] == 'C')
{
int l = a - 1 < 0? 0:a - 1;
int r = b + 1 > n? n:b + 1;
update(0,l,0,0,n,1);
update(r,n,0,0,n,1);
}
if (op[0] == 'C' || op[0] == 'S')
update(a,b,3,0,n,1);
}

memset(hash,false,sizeof(hash));
int s = -1,e = -1;
bool flag = false;
query(0,n,1);
for (int i = 0; i <= n; ++i)
{
if (hash[i])
{
if (s == -1) s = i;
e = i;
}
else
{
if (s != -1)
{
if (flag) printf(" ");
flag = true;
if (s&1) printf("%c%d,",'(',s/2);
else printf("%c%d,",'[',s/2);

if (e&1) printf("%d%c",(e + 1)/2,')');
else printf("%d%c",e/2,']');
s = -1;
}
}
}
if (!flag) printf("empty set");
puts("");
return 0;
}