专题练习:
http://acm.hdu.edu.cn/webcontest/contest_show.php?cid=1441
密码是: cugbacm
首先是准备工作,以下是常见的线段树初始定义:
#include<cstring>
#include<algorithm>
using namespace std;
typedef __int64 lld;
#define ls rt<<1
#define rs rt<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
#define test(a,b) for(int ii=0;ii<(b);ii++)printf("%d%c",(a)[ii],ii==(b)-1?'\n':' ')
#define cntd(a,b) unique((a),(a)+(b))-(a)
#define lb(a,b,c) lower_bound((a),(a)+(b),(c))-(a)
//加入三个函数,test测试,cntd统计不同元素个数,lb找第一个>=c的数的位置。
const int N = 100010;
//线段树N<<2
/*
说明:
unique(num,mun+n)返回的是num去重后的尾地址,并不是真正把重复的元素删除,而
是,该函数把重复的元素移到后面去了,依然保存到了原数组中,函数返回去重后最
后一个元素的地址,因为unique去除的是相邻的重复元素,所以一般用之前都会要排
一下序。
lower_bound(num,num+n,x)返回的是num前n个数中出现的第一个“>=x”的数的位置。
注意:这两个函数用之前都要排序。
使用:
test(key,n); //测试数组key前n个数(key[0~n-1])的值;
sort(key,key+tot);
tot=cntd(key,tot);//统计key[0~tot-1]中不重复元素个数;
pos=lb(key,tot,x[i]);//在key[0~tot-1]中找到第一个“>= x[i]”的元素的位置;
*/
然后要有一个意识,线段树的[l,r]可能值是多少,如为:key[0~tot-1];(tot为不重复元素个数),用
key记录的是排好序的元素值,然后是线段树:sum[N<<2]记录的则是每段的值,[l,r]对应key[]中[l,r];
线段树函数一般格式:
一般地,询问段是pushup ,询问点是pushdown,视题目而定。
一、加点问段型:
void pushup(int rt){ //向上更新
for(int i=0;i<5;i++){
s[rt][i]=s[ls][i]+s[rs][(i-sum[ls]%5+5)%5];
}
}
void build(int l,int r,int rt){//建树
if(l==r){
num[rt]=a[l];
return;
}
int m=(r+l)>>1;
build(lson);
build(rson);
num[rt]=num[ls]+num[rs];//递归完后将值传给根节点,这句相当于pushup
}
void add(int x,int y,int l,int r,int rt){//对点操作
if(l==r){
if(x==l) num[rt]+=y;//到了叶子节点
return;
}
int m=(r+l)>>1;
if(x<=m) add(x,y,lson);
if(x>=m+1) add(x,y,rson);
num[rt]=num[ls]+num[rs];
}
void query(int ql,int qr,int l,int r,int rt){//询问段
if(ql<=l && qr>=r){
ans+=num[rt];//所有分段的和,ans是全局变量
return;
}
int m=(r+l)>>1;
if(ql<=m) query(ql,qr,lson);
if(qr>=m+1) query(ql,qr,rson);
}
二、加段问点型:
void pushdown(int rt){ //向下更新
if(aby[rt]!=0){
aby[ls]+=aby[rt];
aby[rs]+=aby[rt];
aby[rt]=0;
}
}
void build(int l, int r ,int rt){//建树
aby[rt]=0;//初始化
if(l==r){
aby[rt]=num[l];
return;
}
int m=(l+r)>>1;
build(lson);
build(rson);
}
void updata(int ql,int qr,int val,int l,int r,int rt){//对段更新
if(ql<=y[l] && qr>=y[r]){
aby[rt]+=val;//操作段覆盖了树中段则更新,再往下传
return;
}
pushdown(rt);
int m=(l+r)>>1;
if(ql<=y[m]) updata(ql,qr,val,lson);
if(qr>=y[m+1]) updata(ql,qr,val,rson);
}
int query(int yy,int l,int r,int rt){//询问点
if(l==r) return rt;//寻找yy在树中的位置
pushdown(rt);
pushdown(rt);
int m=(l+r)>>1;
if(yy<=y[m]) return query(yy,lson);
return query(yy,rson);
}
树状数组也有类似的操作:
一、加点问段:
void add(int i,int j){ //加点
while(i<N){
sum[i]+=j;
i+=(i&(-i));
}
}
int query(int i){ //问段[a,b] = query(a)-query(b-1)
int ans=0;
while(i>0){
ans+=sum[i];
i-=(i&(-i));
}
return ans;
}
二、加段问点则为:
void add(int i,int x){//加段[a,b]+x : add(a-1,-1); add(b,1);
while(i>0){
sum[i]+=x;
i-=(i&(-i));
}
}
int query(int i){ //问点 = query(i) + ori[i];
int ans=0;
while(i<N){
ans+=sum[i];
i+=(i&(-i));
}
return ans;
}