P 4513 单点修改
#include<cstdio> #include<cstring> #include<algorithm> #include<cctype> #define ll long long #define INF 0x3fffff #define clr(x) memset(x,0,sizeof(x)) using namespace std; inline int read() { register int ret=0,c=getchar(),b=1; while(!isdigit(c))b=c=='-'?-1:1,c=getchar(); while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*b; } #define M 100005 struct tree2 { tree2 *lson,*rson; ll x,lazy; }dizhi[M<<1],*root=&dizhi[0]; int n,m,t=1,a[M]; void bulid(tree2 *tree,int l,int r) { if(l==r) { tree->x=a[l]; return ; } int mid=(l+r)>>1; tree->lson=&dizhi[t++]; tree->rson=&dizhi[t++]; bulid(tree->lson,l,mid); bulid(tree->rson,mid+1,r); tree->x=tree->lson->x+tree->rson->x; } void pushdown(tree2 *tree,int l,int r) { if(!tree->lazy)return ; int mid=(l+r)>>1; tree->lson->x+=tree->lazy*(mid-l+1); tree->rson->x+=tree->lazy*(r-mid); tree->lson->lazy+=tree->lazy; tree->rson->lazy+=tree->lazy; tree->lazy=0; } void change(tree2 *tree,int l,int r,int x,int y,int d) { if(x<=l&&y>=r) { tree->x+=(ll)d*(r-l+1); tree->lazy+=d; return ; } pushdown(tree,l,r); int mid=(l+r)>>1; if(x<=mid)change(tree->lson,l,mid,x,y,d); if(y>mid) change(tree->rson,mid+1,r,x,y,d); tree->x=tree->lson->x+tree->rson->x; } ll query(tree2 *tree,int l,int r,int x,int y) { if(x<=l&&y>=r) return tree->x; pushdown(tree,l,r); int mid=(l+r)>>1; ll t1=0,t2=0; if(x<=mid)t1=query(tree->lson,l,mid,x,y); if(y>mid)t2=query(tree->rson,mid+1,r,x,y); return t1+t2; } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); bulid(root,1,n); for(int i=1;i<=m;i++) { int mode=read(); if(mode==1) { int a=read(),b=read(),c=read(); change(root,1,n,a,b,c); } else { int a=read(),b=read(); printf("%lld ",query(root,1,n,a,b)); } }
ST表
算法用途
Sparse Table,又称ST表,稀疏表。运用倍增的思想,可以解决RMQ,LCA等问题。其优点是在线查询。预处理复杂度为O(nlogn),查询复杂度为O(1)。
算法思想
运用倍增的思想,num[i][j]表示区间[i,i+(1<<
j)]的值。然后进行预处理求出num数组。
算法实现
以求最大值为例:
① 对于预处理,有如下转移方程式:
num[i][j]=max(num[i][j-1],num[i+(1<<j-1)][j-1]);
- 1
这是什么东西啊??
我们来推一遍:
其实只是把[i,i+(1<<
j)]这个区间给分成两块,一块是[i,i+(1<<j-1)],另一块是[i+(1<<j-1),i+(1<<j)](i+(1<<j)==i+(1<<j-1)+(1<<
j-1))。
所以这个区间的最大值就是这两个区间的最大值的较大者。
然后就推好啦!
② 对于查询[x,y]之间的最大值,我们可以这样:
int j=log2(y-x+1);
printf("%d
",max(num[x][j],num[y-(1<<j)+1][j]));
这又是什么东西??
我们再来推一遍:
num[x][j]表示[x,x+(1<<
j)]这段区间,本来是刚好的,但是因为j是向下取整的,因此不一定取得完。取到的区间长度实际为1<<j-1。此时我们可以从y-(1<<j-1)=y-(1<<j)+1这个点开始取(取重了也没事),取相同的长度。然后在这两者之间取较大者即可。
P3865
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define MAXN 150000 using namespace std; int n,m; int num[MAXN+5][18]; inline char readc(){ static char buf[100000],*l=buf,*r=buf; if (l==r) r=(l=buf)+fread(buf,1,100000,stdin); if (l==r) return EOF; return *l++; } int _read(){ int num=0; char ch=readc(); while (ch<'0'||ch>'9') ch=readc(); while (ch>='0'&&ch<='9'){ num=num*10+ch-48; ch=readc(); } return num; } void make(){ for (int i=1;i<18;i++) for (int j=1;j+(1<<i)-1<=n;j++) num[j][i]=max(num[j][i-1],num[j+(1<<i-1)][i-1]); } int main(){ n=_read(); m=_read(); for (int i=1;i<=n;i++) num[i][0]=_read(); make(); for (int i=1;i<=m;i++){ int x=_read(),y=_read(); int j=log2(y-x+1); printf("%d ",max(num[x][j],num[y-(1<<j)+1][j])); } return 0; }
线段树 扫描线
hdu1542
/* 1.保存矩形的上下边界,并且重要的,记录他们是属于上还是下,然后按高度升序排序 2.保存竖线坐标,并且去重,是为了离散化 3.以保存的上下边界数组去更新 */ #include <cstdio> #include <cstring> #include<iostream> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f #define MAX 110 #define LCH(i) ((i)<<1) #define RCH(i) ((i)<<1 | 1) struct segment //保存矩形上下边界 { double l,r,h; //左右横坐标,纵坐标 int f; //-1为下边界,1为上边界 }ss[2*MAX]; struct node //线段树节点 { int l,r; int cnt; //该节点被覆盖的情况 double len; //该区间被覆盖的总长度 int mid() { return (l+r)>>1; } }tt[2*MAX*4]; double pos[2*MAX]; int nums; int cmp(struct segment a ,struct segment b) { return a.h<b.h; } void build(int a, int b ,int rt) { tt[rt].l=a; tt[rt].r=b; tt[rt].cnt=0; tt[rt].len=0; if(a==b) return ; int mid=tt[rt].mid(); build(a,mid,LCH(rt)); build(mid+1,b,RCH(rt)); } int binary(double key ,int low, int high) { while(low<=high) { int mid=(low+high)>>1; if(pos[mid] == key) return mid; else if(key < pos[mid]) high=mid-1; else low=mid+1; } return -1; } void get_len(int rt) { if(tt[rt].cnt) //非0,已经被整段覆盖 tt[rt].len = pos[tt[rt].r+1] - pos[tt[rt].l]; else if(tt[rt].l == tt[rt].r) //已经不是一条线段 tt[rt].len = 0; else //是一条线段但是又没有整段覆盖,那么只能从左右孩子的信息中获取 tt[rt].len = tt[LCH(rt)].len + tt[RCH(rt)].len ; } void updata(int a, int b ,int val ,int rt) { if(tt[rt].l==a && tt[rt].r==b) //目标区间 { tt[rt].cnt += val; //更新这个区间被覆盖的情况 get_len(rt); //更新这个区间被覆盖的总长度 return ; } int mid=tt[rt].mid(); if(b<=mid) //只访问左孩子 updata(a,b,val,LCH(rt)); else if(a>mid) //只访问有孩子 updata(a,b,val,RCH(rt)); else //左右都要访问 { updata(a,mid,val,LCH(rt)); updata(mid+1,b,val,RCH(rt)); } get_len(rt); //计算该区间被覆盖的总长度 } int main() { int Case=0; int n; while(scanf("%d",&n)!=EOF && n) { nums=0; for(int i=0; i<n; i++) { double x1,y1,x2,y2; scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=1; //记录上边界的信息 ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=-1; //记录下边界的信息 pos[nums]=x1; pos[nums+1]=x2; //记录横坐标 nums += 2; } sort(ss,ss+nums,cmp); //横线按纵坐标升序排序 sort(pos,pos+nums); //横坐标升序排序 //for(int i=0; i<nums; i++) printf("%.2lf %.2lf %.2lf ",ss[i].l,ss[i].r,ss[i].h); int m=1; for(int i=1; i<nums; i++) if(pos[i]!=pos[i-1]) //去重 pos[m++]=pos[i]; build(0,m-1,1); //离散化后的区间就是[0,m-1],以此建树 double ans=0; for(int i=0; i<nums; i++) //拿出每条横线并且更新 { int l=binary(ss[i].l,0,m-1); int r=binary(ss[i].r,0,m-1)-1; updata(l,r,ss[i].f,1); //用这条线段去更新 ans += (ss[i+1].h-ss[i].h)*tt[1].len; //printf("%.2lf ",ans); } printf("Test case #%d ",++Case); printf("Total explored area: %.2f ",ans); } return 0; }
P1904(离散化)