线段树入门题
先上板子(不过线段树的使用经常会有要修改的地方,需要自己能够独立的敲出来,这个和图论的那些板子就不一样了)
单点更新,区间和查询
/******************************** 线段树的单点更新和区间和查询 ********************************/ #include <bits/stdc++.h> using namespace std; #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=50005; int tree[MAXN<<2]; void push_up(int rt) { tree[rt]=tree[rt<<1]+tree[rt<<1|1]; } void build(int l,int r,int rt) { if(l==r) { scanf("%d",&tree[rt]); return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } void update(int x,int v,int l,int r,int rt) { if(l==r) { tree[rt]=v; return ; } int m=(l+r)>>1; if(x<=m) update(x, v, ls); else update(x, v, rs); push_up(rt); } int query(int L, int R, int l, int r, int rt) { if(L<=l && r<=R) return tree[rt]; int m=(l+r)>>1, ret=0; if(L<=m) ret+=query(L, R, ls); if(R> m) ret+=query(L, R, rs); return ret; } int main() { return 0; }
单点更新,区间最值查询
/******************************* 线段树的单点更新和区间最值查询 *******************************/ #include <bits/stdc++.h> using namespace std; #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=50005; int tree[MAXN<<2]; void push_up(int rt) { tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); } void build(int l,int r,int rt) { if(l==r) { scanf("%d",&tree[rt]); return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } void update(int x, int v, int l, int r, int rt) { if(l==r) { tree[rt]=v; return ; } int m=(l+r)>>1; if(x<=m) update(x, v, ls); else update(x, v, rs); push_up(rt); } int query(int L, int R, int l, int r, int rt) { if(L<=l && r<=R) return tree[rt]; int m=(l+r)>>1, ret=-1; if(L<=m) ret=max(ret, query(L, R, ls)); if(R> m) ret=max(ret, query(L, R, rs)); return ret; } int main() { return 0; }
区间更新,区间和查询
注意:每次往下层query或者update的之前都需要延迟标记下放
/*************************** 线段树的区间更新和区间和查询 ***************************/ #include <bits/stdc++.h> using namespace std; #define ll long long #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=50005; ll tree[MAXN<<2], add[MAXN<<2]; void push_up(int rt) { tree[rt]=tree[rt<<1]+tree[rt<<1|1]; } void push_down(int rt, int m)//m是当前节点区间的范围的长度 { if(add[rt]) { add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; tree[rt<<1]+=(m-(m>>1))*add[rt]; tree[rt<<1|1]+=(m>>1)*add[rt]; add[rt]=0; } } void build(int l, int r, int rt) { add[rt]=0; if(l==r) { scanf("%lld",&tree[rt]); return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } void update(int L, int R, int c, int l, int r, int rt) { if(L<=l && r<=R) { add[rt]+=c; tree[rt]+=1ll*c*(r-l+1); return ; } push_down(rt, r-l+1); int m=(l+r)>>1; if(L<=m) update(L, R, c, ls); if(R> m) update(L, R, c, rs); push_up(rt); } ll query(int L,int R,int l,int r,int rt) { if(L<=l && r<=R) return tree[rt]; push_down(rt, r-l+1); int m=(l+r)>>1; ll ret=0; if(L<=m) ret+=query(L, R, ls); if(R> m) ret+=query(L, R, rs); return ret; } int main() { return 0; }
POJ 3468(模板题,验板)
/*************************** 线段树的区间更新和区间和查询 ***************************/ //#include <bits/stdc++.h> #include <cstdio> using namespace std; #define ll long long #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=1e5+5; ll tree[MAXN<<2], add[MAXN<<2]; void push_up(int rt) { tree[rt]=tree[rt<<1]+tree[rt<<1|1]; } void push_down(int rt, int m)//m是当前节点区间的范围的长度 { if(add[rt]) { add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; tree[rt<<1]+=(m-(m>>1))*add[rt]; tree[rt<<1|1]+=(m>>1)*add[rt]; add[rt]=0; } } void build(int l, int r, int rt) { add[rt]=0; if(l==r) { scanf("%lld",&tree[rt]); return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } void update(int L, int R, int c, int l, int r, int rt) { if(L<=l && r<=R) { add[rt]+=c; tree[rt]+=1ll*c*(r-l+1); return ; } push_down(rt, r-l+1); int m=(l+r)>>1; if(L<=m) update(L, R, c, ls); if(R> m) update(L, R, c, rs); push_up(rt); } ll query(int L, int R, int l, int r, int rt) { if(L<=l && r<=R) return tree[rt]; push_down(rt, r-l+1); int m=(l+r)>>1; ll ret=0; if(L<=m) ret+=query(L, R, ls); if(R> m) ret+=query(L, R, rs); return ret; } int main() { int n, q; scanf("%d%d", &n, &q); build(1, n, 1); while(q--) { char cmd[10]; int a, b, c; scanf("%s%d%d", cmd, &a, &b); //scanf("%c")是会读取会回车的... if(cmd[0]=='Q') printf("%lld\n", query(a, b, 1, n, 1)); else { scanf("%d", &c); update(a, b, c, 1, n, 1); } } return 0; }
HDU 1394(求逆序对的个数)
注意:如果给出的数据不是离散且连续的话,需要先离散化。
#include <bits/stdc++.h> using namespace std; #define _for(i,a,b) for(int i=(a); i< (b); i++) #define _rep(i,a,b) for(int i=(a); i<=(b); i++) #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=50005; int tree[MAXN<<2]; void push_up(int rt) { tree[rt]=tree[rt<<1]+tree[rt<<1|1]; } void build(int l,int r,int rt) { if(l==r) { tree[rt]=0; return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } void update(int x,int v,int l,int r,int rt) { if(l==r) { tree[rt]=v; return ; } int m=(l+r)>>1; if(x<=m) update(x, v, ls); else update(x, v, rs); push_up(rt); } int query(int L, int R, int l, int r, int rt) { if(L<=l && r<=R) return tree[rt]; int m=(l+r)>>1, ret=0; if(L<=m) ret+=query(L, R, ls); if(R> m) ret+=query(L, R, rs); return ret; } int a[MAXN]; int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n; while(cin>>n) { int ans=0, sum=0; build(0, n-1, 1); _for(i, 0, n) { cin>>a[i]; sum+=query(a[i]+1, n-1, 0, n-1, 1); update(a[i], 1, 0, n-1, 1); } ans=sum; _for(i, 0, n) { sum+=(n-1-a[i])-a[i]; ans=min(ans, sum); } cout<<ans<<endl; } return 0; }
HDU 2795
题意:h*w的木板,放进一些1*L的物品(横着放)优先往上层放,求层数,不能放的话-1
题解:线段树的底部对应h,值对应该层剩余的最大空间,向上更新最大值,每一次查询优先往左子树走,查询后进行更改,并向上更新。
还有一点,这个h的范围很大,但是k的范围较小,h最多只要k层即可。
备注:数据量很大输出很多,发现一个问题,cout时候<<endl 5100ms, <<"\n" 1600ms(比scanf还要快)
#include <bits/stdc++.h> using namespace std; #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=2e5+5; int tree[MAXN<<2]; int h, w, k; void push_up(int rt) { tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); } void build(int l,int r,int rt) { if(l==r) { tree[rt]=w; return ; } int m=(l+r)>>1; build(ls); build(rs); push_up(rt); } int query(int x, int l, int r, int rt) //单点查询和更新 { if(l==r){ tree[rt]-=x; return l; } int m=l+r>>1; int ret=(tree[rt<<1]>=x? query(x, ls):query(x, rs)); //优先往左子树走 push_up(rt); return ret; } int main() { //freopen("in.txt", "r", stdin); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); while(cin>>h>>w>>k) { if(h>k) h=k; build(1, h, 1); while(k--) { int x; cin>>x; if(tree[1]<x) cout<<"-1"<<"\n"; else cout<<query(x, 1, h, 1)<<"\n"; } } return 0; }
POJ 2528 (离散化+线段树)
题意:按次序给你贴广告时的位置(左端点和右端点),求能看得见多少个广告牌。
题解:此题中l和r的范围很大,需要对点的坐标离散化(不改变数据的相对大小下(不影响结果),缩小数据的范围)。
底层表示总区间,从后往前进行查询并覆盖,判断是否能看见。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 const int MAXN=2e4+5, MAXM=1e7+5; int tree[MAXN<<2], pos[MAXN], pl[MAXN], pr[MAXN]; int hash[MAXM]; void push_up(int rt) { tree[rt]=tree[rt<<1] && tree[rt<<1|1]; } void build(int l,int r,int rt) { tree[rt]=0; if(l==r) return ; int m=(l+r)>>1; build(ls); build(rs); } int query(int L, int R, int l, int r, int rt) { //注意这个区间修改的过程中,因为这个只是0或者1,所以只用修改到完全包含该区间的位置即可(就是放延迟标记的地方即可)不用push_down //往下查询的时候先判断是否被标记即可 (其实就是我们要查询的地方是否被延迟标记过了) //同理,建树的时候,每一个节点都要归0(push_up加上去即可,或者直接赋值),都把我给调傻了....(其实感觉直接延迟标记成段更新反而没有这么多的细节Orz) if(tree[rt]) return 0; if(L<=l && r<=R){ tree[rt]=1; return 1; } int m=l+r>>1; /* int okl=1, okr=1; WA if(L<=m) okl=query(L, R, ls); if(R> m) okr=query(L, R, rs); ok=okl||okr; */ int ok, okl, okr; if(L>m) ok=query(L, R, rs); else if(R<=m) ok=query(L, R, ls); else { okl=query(L, R, ls); okr=query(L, R, rs); ok=okl || okr; } push_up(rt); return ok; } void print(int l, int r, int rt) //debug { printf("! l=%d, r=%d, is_ok=%d\n", l, r, tree[rt]); if(l==r) return; int m=l+r>>1; print(ls); print(rs); } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); //注意调试的时候用printf的时候不要解除流绑定 // freopen("in.txt", "r", stdin); int T; for(cin>>T; T--; ) { int n; cin>>n; for(int i=0; i<n; i++) { cin>>pl[i]>>pr[i]; pos[2*i]=pl[i]; pos[2*i+1]=pr[i]; } sort(pos, pos+2*n); int cnt=unique(pos, pos+2*n)-pos; for(int i=0; i<cnt; i++) hash[pos[i]]=i; build(0, cnt-1, 1); int ans=0; for(int i=n-1; i>=0; i--) { if(query(hash[pl[i]], hash[pr[i]], 0, cnt-1, 1)) { //printf("! i=%d,l=%d, r=%d\n", i, hash[pl[i]], hash[pr[i]]); ans++; //print(0, cnt-1, 1); //printf("\n"); } } cout<<ans<<"\n"; } return 0; }
HDU 1542 矩形面积并(线段树+离散化+扫描线)
题意:求多个矩形并后的总面积。
题解:扫描线,从下往上扫,cnt记录每一个区间的下位边和上位边的差值,sum记录每个区间下该条扫描线的有效覆盖长度。
用线段树维护这两个值,push_up分几种情况,别漏了。
数据是浮点数,离散化线段树的l,r和对应每一段区间的编号即可。
备注:这题调了好久,1,sum在计算的时候出现了负数,发现ls和rs预定义的时候<<写反了(太sb了)。 2.用lower_bound查询会MLE(等我去查查)。
#include <bits/stdc++.h> using namespace std; #define ls l, m, rt<<1 #define rs m+1, r, rt<<1|1 const int MAXN=2e3+5; double sum[MAXN<<2], X[MAXN]; int cnt[MAXN<<2]; struct Line{ double l, r, h; int f; Line() {} Line(double a, double b, double c, int d) : l(a), r(b), h(c), f(d) {} bool operator <(const Line &b) const { return h<b.h; } }lines[MAXN]; void push_up(int rt, int l, int r) { if(cnt[rt]) sum[rt]=X[r+1]-X[l]; //该区间被完全覆盖,注意cnt的范围一定是>=0的 else if(l==r) sum[rt]=0; //该区间是叶子节点,且cnt==0 else sum[rt]=sum[rt<<1]+sum[rt<<1|1]; //该区间不是叶子节点,且没有被完全覆盖 } void update(int L, int R, int c, int l, int r, int rt) { if(L<=l && r<=R){ cnt[rt]+=c; push_up(rt, l, r); return ; } int m=l+r>>1; if(L<=m) update(L, R, c, ls); if(R> m) update(L, R, c, rs); push_up(rt, l, r); } int bin(double val, int l, int r, double x[]) { while(l<=r) { int m=l+r>>1; if(x[m]==val) return m; else if(x[m]<val) l=m+1; else if(x[m]>val) r=m-1; } return -1; } int main() { //freopen("in.txt", "r", stdin); int n, kase=0; while(cin>>n, n) { int m=0; for(int i=0; i<n; i++) { double x1, y1, x2, y2; cin>>x1>>y1>>x2>>y2; X[m]=x1; lines[m++]=Line(x1, x2, y1, 1); X[m]=x2; lines[m++]=Line(x1, x2, y2, -1); } sort(X, X+m); sort(lines, lines+m); int k=unique(X, X+m)-X; memset(sum, 0, sizeof(sum)); memset(cnt, 0, sizeof(cnt)); //build double ans=0; for(int i=0; i<m-1; i++) { //int l=lower_bound(X, X+m, lines[i].l)-X; //int r=lower_bound(X, X+m, lines[i].r)-X-1; MLE 不明白为啥会这样 int l=bin(lines[i].l, 0, k-1, X); int r=bin(lines[i].r, 0, k-1, X)-1; if(l<=r) update(l, r, lines[i].f, 0, k-1, 1); ans+=sum[1]*(lines[i+1].h-lines[i].h); } printf("Test case #%d\nTotal explored area: %.2lf\n\n",++kase, ans); } return 0; }
POJ 3667 区间合并
题意: 2个指令1 a:询问是不是有连续长度为a的空房间,输出最左边起始的位置,并占领该段区间 2 a b:将[a,a+b-1]的房间清空
题解:区间合并入门题。线段树维护左右子树,左边,中间,右边,和交叉的空闲区间的长度。
//#include <bits/stdc++.h> #include <iostream> #include <cstdio> using namespace std; #define ls l, m, rt<<1 #define rs m+1, r, rt<<1|1 const int MAXN=5e4+5; int cover[MAXN<<2]; int lsum[MAXN<<2], rsum[MAXN<<2], msum[MAXN<<2]; void push_up(int rt, int m) { lsum[rt]=lsum[rt<<1]; rsum[rt]=rsum[rt<<1|1]; if(lsum[rt]==m-m/2) lsum[rt]+=lsum[rt<<1|1]; //注意这里别写成了m-m>>1 if(rsum[rt]==m/2 ) rsum[rt]+=rsum[rt<<1]; msum[rt]=max(rsum[rt<<1]+lsum[rt<<1|1], max(msum[rt<<1], msum[rt<<1|1])); //分3种情况 } void push_down(int rt, int m) { if(cover[rt]!=-1) { cover[rt<<1]=cover[rt<<1|1]=cover[rt]; lsum[rt<<1]=rsum[rt<<1]=msum[rt<<1]= cover[rt]? 0:m-m/2; lsum[rt<<1|1]=rsum[rt<<1|1]=msum[rt<<1|1]= cover[rt]? 0:m/2; cover[rt]=-1; } } void build(int l, int r, int rt) { lsum[rt]=rsum[rt]=msum[rt]=r-l+1; cover[rt]=-1; //归为-1,更新的时候会放0,或者1的延迟标记 if(l==r) return ; int m=l+r>>1; build(ls); build(rs); } int query(int a, int l, int r, int rt) { if(l==r) return l; push_down(rt, r-l+1); int m=l+r>>1; if(msum[rt<<1]>=a) return query(a, ls); //优先向左走,三种情况,左中右 else if(rsum[rt<<1]+lsum[rt<<1|1]>=a) return m-rsum[rt<<1]+1; else return query(a, rs); } void update(int L, int R, int v, int l, int r, int rt) { if(L<=l && r<=R) { lsum[rt]=rsum[rt]=msum[rt]= v? 0:r-l+1; cover[rt]=v; return ; } push_down(rt, r-l+1); int m=l+r>>1; if(L<=m) update(L, R, v, ls); if(R >m) update(L, R, v, rs); push_up(rt, r-l+1); } void print(int l, int r, int rt) { printf("! l=%d, r=%d, lsum=%d, rsum=%d, msum=%d\n", l, r, lsum[rt], rsum[rt], msum[rt]); if(l==r) return ; int m=l+r>>1; print(ls); print(rs); } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); //freopen("in.txt", "r", stdin); int n, m; cin>>n>>m; build(1, n, 1); while(m--) { int cmd, a, b; cin>>cmd; if(cmd==1){ cin>>a; if(msum[1]<a){ cout<<"0\n"; continue; } int pos=query(a, 1, n, 1); cout<<pos<<"\n"; update(pos, pos+a-1, 1, 1, n, 1); } else{ cin>>a>>b; update(a, a+b-1, 0, 1, n, 1); } // printf("\n"); // print(1, n, 1); // printf("\n"); } return 0; }