线段树练习——成段更新
这一块关键理解延迟标记(或者说懒惰标记)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<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 <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 <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 <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; }