修改自:http://www.notonlysuccess.com/
在代码前先介绍一些我的线段树风格:
N是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于N的最小2x的两倍
lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示
以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便
PushUp(int rt)是把当前结点的信息更新到父结点
PushDown(int rt)是把当前结点的信息更新给儿子结点
rt表示当前子树的根(root),也就是当前所在的结点
整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:
1.单点更新:
最最基础的线段树,只更新叶子节点,然后把信息用PushUp(int r)这个函数更新上来
题意:O(-1)
思路:O(-1)
线段树功能:update:单点增减 query:区间求和
//单点增减 区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=55555;
int sum[N<<2];//四倍
void PushUp(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
if(l==r){
scanf("%d",&sum[rt]);
return ;
}
int m=(l+r)>>1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int p,int add,int l,int r,int rt){
if(l==r){
sum[rt]+=add;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p, add, lson);
else update(p, add, rson);
PushUp(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
return sum[rt];
}
int m=(l+r)>>1;
int ret=0;
if(L<=m)ret+=query(L, R, lson);
if(R>m)ret+=query(L, R, rson);
return ret;
}
int main(){
int T,n;
scanf("%d",&T);
for(int cas=1; cas<=T; cas++){
printf("Case %d:\n",cas);
scanf("%d",&n);
build(1, n, 1);
char op[10];
while(scanf("%s",op)){
if(op[0]=='E')break;
int a,b;
scanf("%d%d",&a,&b);
if(op[0]=='Q')printf("%d\n",query(a, b, 1, n, 1));
else if(op[0]=='S')update(a, -b, 1, n , 1);
else update(a, b, 1, n, 1);
}
}
return 0;
}
题意:O(-1)
思路:O(-1)
线段树功能:update:单点替换 query:区间最值
//单点替换 区间最值
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=222222;
int Max[N<<2];
void PushUp(int rt){
Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);
}
void build(int l,int r,int rt){
if(l==r){
scanf("%d",&Max[rt]);
return ;
}
int m=(l+r)>>1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int p,int re,int l,int r,int rt){
if(l==r){
Max[rt]=re;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p, re, lson);
else update(p, re, rson);
PushUp(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
return Max[rt];
}
int m=(l+r)>>1;
int ret=0;
if(L<=m)ret=max(ret, query(L, R, lson));
if(R>m)ret=max(ret,query(L, R, rson));
return ret;
}
int main(){
int n,m;
while(~scanf("%d%d",&n,&m)){
build(1, n, 1);
int a,b;
char op[2];
while(m--){
scanf("%s%d%d",op,&a,&b);
if(op[0]=='Q')printf("%d\n",query(a, b, 1, n, 1));
else update(a, b, 1, n, 1);
}
}
return 0;
}
hdu1394 Minimum Inversion Number
题意:求Inversion后的最小逆序数个数
思路:
先百科一下逆序数的概念: 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个 逆序 。一个排列中逆序的总数就称为这个排列的 逆序数 。逆序数为 偶数 的排列称为 偶排列 ;逆序数为奇数的排列称为 奇排列 。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。
逆序数计算方法是:在逐个元素读取原始数列时,每次读取都要查询当前已读取元素中大于当前元素的个数,加到sum里,最后得到的sum即为原始数列的逆序数。
接下来找数列平移后得到的最小逆序数,假设当前序列逆序数是sum,那么将a[0]移到尾部后逆序数的改变是之前比a[0]大的数全部与尾部a[0]组合成逆序数,假设数量为x,则x=n-1-a[0],而之前比a[0]小的数(也就是之前能和a[0]组合为逆序数的元素)不再与a[0]组合成逆序数,假设数量为y,则y=n-x-1,这样,新序列的逆序数就是sum+x-y=sum-2*a[0]+n-1;
接下来说明下线段树的作用,线段区间表示当前已读取的元素个数,比如[m,n]表示在数字m到n之间有多少个数已经读入,build时所有树节点全部为0就是因为尚未读数,update函数是将新读入的数字更新到线段树里,点更新,query函数是查询当前数字区间已存在的数字个数。
用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解
线段树功能:update:单点增减 query:区间求和
//关键在于如何用线段树维护
//方法:暴力或者线段树或者归并排序
//单点替换 区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=5555;
int sum[N<<2];
int a[N];
void PushUp(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
sum[rt]=0;
if(l==r)return ;
int m=(l+r)>>1;
build(lson);
build(rson);
}
void update(int p,int l,int r,int rt){
if(l==r){
sum[rt]=1;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p, lson);
else update(p, rson);
PushUp(rt);
}
//询问区间[L, R]中目前的数字个数
int query(int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
return sum[rt];
}
int m=(l+r)>>1;
int ret=0;
if(L<=m)ret+=query(L, R, lson);
if(R>m)ret+=query(L, R, rson);
return ret;
}
int main(){
int n;
while(~scanf("%d",&n)){
build(0, n-1, 1);
int sum=0;//求原始数列的逆序数
for(int i=0; i<n; i++){
scanf("%d",&a[i]);
//查询当前比a[i]大的数有多少个
sum+=query(a[i], n-1, 0, n-1, 1);
update(a[i], 0, n-1, 1);
}
int ans=sum;
for(int i=0; i<n; i++){
sum+=n- 2*a[i] -1;
ans=min(ans, sum);
}
printf("%d\n",ans);
}
return 0;
}
题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子
思路:每次找到最大值的位子,然后减去L
线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)
//单点增减 区间最值
/*************************************************
题目大意:
有一块h*w的矩形广告板,要往上面贴广告;
然后给n个1*wi的广告,要求把广告贴上去;
而且要求广告要尽量往上贴并且尽量靠左;
求第n个广告的所在的位置,不能贴则为-1;
算法思想:
利用线段树可以求区间的最大值;
将位置即h用来建树(h<=n,大了没有意义);
树中存储的为该位置还拥有的空间;
若左子树的最大值大于他,就查询左子树,否则查询右子树;
**************************************************/
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=222222;
int Max[N<<2];
int h,w,n;
void PushUp(int rt){
Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);
}
void build(int l,int r,int rt){
Max[rt]=w;
if(l==r)return ;
int m=(l+r)>>1;
build(lson);
build(rson);
}
int query(int x,int l,int r,int rt){
if(l==r){
Max[rt]-=x;
return l;
}
int m=(l+r)>>1;
int ret=0;
ret=(Max[rt<<1]>=x) ? query(x, lson):query(x,rson);
PushUp(rt);
return ret;
}
int main(){
while(~scanf("%d%d%d",&h,&w,&n)){
if(h>n)h=n;
build(1, h, 1);
int x;
while(n--){
scanf("%d",&x);
if(Max[1]<x)puts("-1");
else printf("%d\n",query(x, 1, h, 1));
}
}
return 0;
}
练习:
poj2828 Buy Tickets
poj2886 Who Gets the Most Candies?
2.成段更新
(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候
题意:O(-1)
思路:O(-1)
线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)
//用延迟标记使得更新延迟到 *下次 *需要 更新or询问到的时候
//成段替换 区间求和
#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=111111;
int sum[N<<2];
int col[N<<2];
void PushUp(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void PushDown(int rt,int m){
if(col[rt]){
col[rt<<1]=col[rt<<1|1]=col[rt];
sum[rt<<1]=(m- (m>>1)) * col[rt];
sum[rt<<1|1]=(m>>1) * col[rt];
col[rt]=0;
}
}
void build(int l,int r,int rt){
col[rt]=0;
sum[rt]=1;
if(l==r)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){
col[rt]=c;
sum[rt]=c*(r-l+1);
return ;
}
PushDown(rt, r-l+1);
int m=(l+r)>>1;
if(L<=m)update(L, R, c, lson);
if(R>m) update(L, R, c, rson);
PushUp(rt);
}
int main(){
int T,n,m;
scanf("%d",&T);
for(int cas=1; cas<=T; cas++){
scanf("%d%d",&n,&m);
build(1, n, 1);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
update(a, b, c, 1, n, 1);
}
printf("Case %d: The total value of the hook is %d.\n",cas,sum[1]);
}
return 0;
}
poj3468 A Simple Problem with Integers
题意:O(-1)
思路:O(-1)
线段树功能:update:成段增减 query:区间求和
//成段增减 区间求和
//G++2594ms C++1735ms
#include <cstdio>
#include <algorithm>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
#define ll long long
using namespace std;
const int N=111111;
ll sum[N<<2];
ll add[N<<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]+=(m- (m>>1)) * add[rt];
sum[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",&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(R>m) 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(R>m)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);
}
}
return 0;
}
题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报
思路:这题数据范围很大,直接搞超时+超内存,需要离散化:
离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了
所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多
而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(poj这题数据奇弱)
如三张海报为:1~10 1~4 6~10
离散化时 X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 6, X[ 4 ] = 10
第一张海报时:墙的1~4被染为1;
第二张海报时:墙的1~2被染为2,3~4仍为1;
第三张海报时:墙的3~4被染为3,1~2仍为2。
最终,第一张海报就显示被完全覆盖了,于是输出2,但实际上明显不是这样,正确输出为3。
为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,4,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,例如在上面1 4 6 10中间加5(算法中实际上1,4之间,6,10之间都新增了数的)
X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 5, X[ 4 ] = 6, X[ 5 ] = 10
这样之后,第一次是1~5被染成1;第二次1~2被染成2;第三次4~5被染成3
最终,1~2为2,3为1,4~5为3,于是输出正确结果3。
线段树功能:update:成段替换 query:简单hash
//C++ 78ms G++ RTE
#include<cstdio>
#include<algorithm>
#include<cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
const int N=11111;
bool hash[N];
int li[N],ri[N];
int x[N*3];
int col[N<<4];
int cnt;
void PushDown(int rt){
if(col[rt]!=-1){
col[rt<<1]=col[rt<<1|1]=col[rt];
col[rt]=-1;
}
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l && r<=R){
col[rt]=c;
return ;
}
PushDown(rt);
int m=(l+r)>>1;
if(L<=m)update(L, R, c, lson);
if(R>m)update(L, R, c, rson);
}
void query(int l,int r,int rt){
if(col[rt]!=-1){
if(!hash[col[rt]])cnt++;
hash[ col[rt] ]=true;
return ;
}
if(l==r)return ;
int m=(l+r)>>1;
query(lson);
query(rson);
}
int bin(int key,int n,int x[]){//二分查找
int l=0,r=n-1;
while(l<=r){
int m=(l+r)>>1;
if(x[m]==key)return m;
if(x[m]<key)l=m+1;
else r=m-1;
}
return -1;
}
int main(){
int T,n;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
int nn=0;
for(int i=0; i<n; i++){
scanf("%d%d",&li[i], &ri[i]);
x[nn++]=li[i];
x[nn++]=ri[i];
}
sort(x,x+nn);
int m=1;
for(int i=1; i<nn; i++)
if(x[i]!=x[i-1])x[m++]=x[i];
for(int i=m-1; i>0; i--)
if(x[i]!=x[i-1]+1)x[m++]=x[i-1]+1;
sort(x,x+m);
Memset(col, -1);
for(int i=0; i<n; i++){
int l=bin(li[i], m, x);
int r=bin(ri[i], m, x);
update(l, r, i, 0, m-1, 1);
}
cnt=0;
Memset(hash, false);
query(0, m-1, 1);
printf("%d\n",cnt);
}
return 0;
}
题意:区间操作,交,并,补等
思路:
我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)
- U T S ← S ∪ T 即将[l,r]标记为1
- I T S ← S ∩ T 即将-oo~l和r~+oo标记为0,因为是并集,所以并集后的集合s一定在[l,r]内,则在l,r内的集合被标记是什么状态就是什么状态(表示是否属于s),[l,r]外的集合不属于s所以标记为0
- D T S ← S - T 即将[l,r]标记为0,则在[l,r]内被s包含的集合也会标记为0表示不再属于s
- C T S ← T - S 即先将-oo~l,r~+oo标记为0,这部分不属于[l,r]则一定不属于s,然后将[l,r]的标记0/1互换,因为属于s的不再属于s,不属于s的将属于s
-
-
-
-
-
- S T S ← S ⊕ T 即属于s的不变,[l,r]中不属于s的(区间)0标记为1,属于s的(区间)1标记为0,所以[l,r]的标记0/1互换
成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作
很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了
所以当一个节点得到覆盖标记时把异或标记清空
而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记
开区间闭区间只要数字乘以2就可以处理(偶数表示包括端点,奇数表示不包括)
线段树功能:update:成段替换,区间异或 query:简单mark
#include <cstdio> #include <cstring> #include <cctype> #include <algorithm> #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 using namespace std; const int maxn=65535*2+2; int cover[maxn<<2],Xor[maxn<<2];//cover表示区间的0,1状态,Xor表示该区间转换的状态 bool mark[maxn]; void UpdateXor(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]){//表示该点转换了但是孩子未转换 UpdateXor(rt<<1); UpdateXor(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'){ UpdateXor(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;//将-oo~l中的-oo~mid标记为0 } if(m<R)update(op, L, R, rson); else if(op=='I'||op=='C'){ Xor[rt<<1|1]=cover[rt<<1|1]=0;//将r~+oo中的mid+1~+oo标记为0 } } void query(int l,int r,int rt){ if(cover[rt]==1){ for(int i=l; i<=r; i++){ mark[i]=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(){ #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif 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){//比如输入区间为I (1,1)或C (1,1) if(op=='C' || op=='I'){//将-oo~l,r~+oo标记为0 cover[1]=Xor[1]=0; } }else update(op, a, b, 0, maxn, 1); } query(0, maxn, 1); bool flag=false; int s=-1,t; for(int i=0; i<=maxn; i++){ if(mark[i]){ if(s==-1)s=i; t=i; }else{ if(s!=-1){ if(flag)printf(" "); //比如[1,3) (3,5] flag=true; printf("%c%d,%d%c",s&1?'(':'[', s>>1, (t+1)>>1, t&1?')':']'); s=-1; } } } if(!flag)printf("empty set"); puts(""); return 0; }
练习:
poj1436 Horizontally Visible Segments
poj2991 Crane
Another LCIS
Bracket Sequence
3.区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并
题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边
2 a b:将[a,a+b-1]的房间清空
思路:记录区间中最长的空房间
线段树操作:update:区间替换 query:询问满足条件的最左断点
#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 = 55555; int lsum[maxn<<2] , rsum[maxn<<2] , msum[maxn<<2]; int cover[maxn<<2]; void PushDown(int rt,int m) { if (cover[rt] != -1) { cover[rt<<1] = cover[rt<<1|1] = cover[rt]; msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = cover[rt] ? 0 : m - (m >> 1); msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = cover[rt] ? 0 : (m >> 1); cover[rt] = -1; } } void PushUp(int rt,int m) { lsum[rt] = lsum[rt<<1]; rsum[rt] = rsum[rt<<1|1]; if (lsum[rt] == m - (m >> 1)) lsum[rt] += lsum[rt<<1|1]; if (rsum[rt] == (m >> 1)) rsum[rt] += rsum[rt<<1]; msum[rt] = max(lsum[rt<<1|1] + rsum[rt<<1] , max(msum[rt<<1] , msum[rt<<1|1])); } void build(int l,int r,int rt) { msum[rt] = lsum[rt] = rsum[rt] = r - l + 1; cover[rt] = -1; if (l == r) return ; int m = (l + r) >> 1; build(lson); build(rson); } void update(int L,int R,int c,int l,int r,int rt) { if (L <= l && r <= R) { msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1; cover[rt] = c; 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 , r - l + 1); } int query(int w,int l,int r,int rt) { if (l == r) return l; PushDown(rt , r - l + 1); int m = (l + r) >> 1; if (msum[rt<<1] >= w) return query(w , lson); else if (rsum[rt<<1] + lsum[rt<<1|1] >= w) return m - rsum[rt<<1] + 1; return query(w , rson); } int main() { int n , m; scanf("%d%d",&n,&m); build(1 , n , 1); while (m --) { int op , a , b; scanf("%d",&op); if (op == 1) { scanf("%d",&a); if (msum[1] < a) puts("0"); else { int p = query(a , 1 , n , 1); printf("%d\n",p); update(p , p + a - 1 , 1 , 1 , n , 1); } } else { scanf("%d%d",&a,&b); update(a , a + b - 1 , 0 , 1 , n , 1); } } return 0; }
练习:
hdu3308 LCIS
hdu3397 Sequence operation
hdu2871 Memory Control
hdu1540 Tunnel Warfare 代码:点击打开链接
CF46-D Parking Lot
扫描线
这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
最典型的就是矩形面积并,周长并等题
题意:矩形面积并 具体可参考:点击打开链接
思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个
线段树操作:update:区间增减 query:直接取根节点的值 代码:点击打开链接
#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 = 2222; int cnt[maxn << 2]; double sum[maxn << 2]; double X[maxn]; struct Seg { double h , l , r;//扫描线的左右端点x坐标,扫描线的高度 int s; //为1或-1,标记扫描线是矩形的上位还是下位边. Seg(){} Seg(double a,double b,double c,int d) : l(a) , r(b) , h(c) , s(d) {} bool operator < (const Seg& rhs) const { return h < rhs.h; } }ss[maxn]; void PushUp(int rt,int l,int r) { if (cnt[rt]) sum[rt] = X[r+1] - X[l]; else if (l == r) sum[rt] = 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; PushUp(rt , l , r); return ; } int m = (l + r) >> 1; if (L <= m) update(L , R , c , lson); if (m < R) update(L , R , c , rson); PushUp(rt , l , r); } int main() { int n , cas = 1; while (~scanf("%d",&n) && n) { int m = 0; while (n --) { double a , b , c , d; scanf("%lf%lf%lf%lf",&a,&b,&c,&d); X[m] = a; ss[m++] = Seg(a , c , b , 1); X[m] = c; ss[m++] = Seg(a , c , d , -1); } sort(X , X + m); //X按从小到大排序 sort(ss , ss + m);//按h值从小到大排序 int k = 1; //对X去重,并且用k表示一共有多少个X for (int i = 1 ; i < m ; i ++) { if (X[i] != X[i-1]) X[k++] = X[i]; }//当我们需要找到第i个区域的两端点坐标时,只需要X[i]和X[i+1] memset(cnt , 0 , sizeof(cnt));//cnt: >=0时表示本节点控制的区域内下位边个数-上位边个数的结果. //如果==-1时,表示本节点左右子树的上下位边数不一致. memset(sum , 0 , sizeof(sum)); //sum: 本节点控制的区域内cnt值不为0的区域总长度. double ret = 0; for (int i = 0 ; i < m - 1 ; i ++) { int l = lower_bound(X, X+k, ss[i].l)-X; int r = lower_bound(X, X+k, ss[i].r)-X-1; if (l <= r) update(l , r , ss[i].s , 0 , k - 1, 1);//线段树从0~k-1,也就是X的下标 ret += sum[1] * (ss[i+1].h - ss[i].h); } printf("Test case #%d\n", cas++); printf("Total explored area: %.2f\n\n", ret); } return 0; }
-
题意:矩形周长并
思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)
线段树操作:update:区间增减 query:直接取根节点的值
#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 = 22222; struct Seg{ int l , r , h , s; Seg() {} Seg(int a,int b,int c,int d):l(a) , r(b) , h(c) , s(d) {} bool operator < (const Seg &cmp) const { return h < cmp.h; } }ss[maxn]; bool lbd[maxn<<2] , rbd[maxn<<2]; int numseg[maxn<<2]; int cnt[maxn<<2]; int len[maxn<<2]; void PushUP(int rt,int l,int r) { if (cnt[rt]) { lbd[rt] = rbd[rt] = 1; len[rt] = r - l + 1; numseg[rt] = 2; } else if (l == r) { len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0; } else { lbd[rt] = lbd[rt<<1]; rbd[rt] = rbd[rt<<1|1]; len[rt] = len[rt<<1] + len[rt<<1|1]; numseg[rt] = numseg[rt<<1] + numseg[rt<<1|1]; if (lbd[rt<<1|1] && rbd[rt<<1]) numseg[rt] -= 2;//两条线重合 } } void update(int L,int R,int c,int l,int r,int rt) { if (L <= l && r <= R) { cnt[rt] += c; PushUP(rt , l , r); return ; } int m = (l + r) >> 1; if (L <= m) update(L , R , c , lson); if (m < R) update(L , R , c , rson); PushUP(rt , l , r); } int main() { int n; while (~scanf("%d",&n)) { int m = 0; int lbd = 10000, rbd = -10000; for (int i = 0 ; i < n ; i ++) { int a , b , c , d; scanf("%d%d%d%d",&a,&b,&c,&d); lbd = min(lbd , a); rbd = max(rbd , c); ss[m++] = Seg(a , c , b , 1); ss[m++] = Seg(a , c , d , -1); } sort(ss , ss + m); int ret = 0 , last = 0; for (int i = 0 ; i < m ; i ++) { if (ss[i].l < ss[i].r) update(ss[i].l , ss[i].r - 1 , ss[i].s , lbd , rbd - 1 , 1); ret += numseg[1] * (ss[i+1].h - ss[i].h); ret += abs(len[1] - last); last = len[1]; } printf("%d\n",ret); } return 0; }
练习:
hdu3265 Posters
hdu3642 Get The Treasury
poj2482 Stars in Your Window
poj2464 Brownie Points II
hdu3255 Farming
ural1707 Hypnotoad’s Secret
uva11983 Weird Advertisement
线段树与其他结合练习
(欢迎大家补充):
hdu3333 Turing Tree
hdu3874 Necklace
hdu3016 Man Down
hdu3340 Rain in ACStar
zju3511 Cake Robbery
UESTC1558 Charitable Exchange
CF85-D Sum of Medians
spojGSS2 Can you answer these queries II