1、建树
a、对于二分到的每一个结点,给它的左右端点确定范围。
b、如果是叶子节点,存储要维护的信息,再回到父节点时累计到父节点去。
c、合并
代码
void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=tree[k*2].w+tree[k*2+1].w; }
单点查询
查询一个点的状态,设待查询点为x
a、如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。
b、如果不是,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid。
c、如果x<=mid,则递归它的左孩子,否则递归它的右孩子
代码
void ask(int k) { if(tree[k].l==tree[k].r) //当前结点的左右端点相等,是叶子节点,是最终答案 { ans=tree[k].w; return ; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) ask(k*2);//目标位置比中点靠左,就递归左孩子 else ask(k*2+1);//反之,递归右孩子 }
单点修改
即更改某一个点的状态。
结合单点查询的原理,找到x的位置;根据建树状态合并的原理,修改每个结点的状态。
void add(int k) { if(tree[k].l==tree[k].r)//找到目标位置 { tree[k].w+=y; return; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) add(k*2); else add(k*2+1); tree[k].w=tree[k*2].w+tree[k*2+1].w;//所有包含结点k的结点状态更新 }
区间查询
代码
void sum(int k) { if(tree[k].l>=x&&tree[k].r<=y) { ans+=tree[k].w; return; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) sum(k*2); if(y>m) sum(k*2+1); }
区间修改
同理
我们不要递归到每个节点。所以要有一个新的概念:懒标记。
就像新年的时候的压岁钱,只有要用的时候才用,不要的直接给父母保管。
所以,传下来的更改值若在一个区间里,就不再下传,修改完该节点信息后,在此节的懒标记上打一个更改值。
当需要递归这个节点的子节点时,标记下传给子节点。这里不必管用哪个子节点,两个都传下去。
①当前节点的懒标记累积到子节点的懒标记中。
②修改子节点状态。在引例中,就是原状态+子节点区间点的个数父节点传下来的懒标记。
③父节点懒标记清0。这个懒标记已经传下去了,欠债还清,不用再还了。
下传代码
void pushdown(int k) { tree[k*2].w+=((tree[k*2].r-tree[k*2].l+1)*tree[k].f); tree[k*2+1].w+=((tree[k*2+1].r-tree[k*2+1].l+1)*tree[k].f); tree[k*2].f+=tree[k].f; tree[k*2+1].f+=tree[k].f; tree[k].f=0; }
区间修改代码
void add(int k,int t,int w) { int mid; if (t>w) return; if (x<=t&&w<=y) { tree[k].w+=((w-t+1)*z); tree[k].f+=z; return ; } mid=(t+w)/2; if (tree[k].f) pushdown(k); if (x<=mid) add(k*2,t,mid); if (y>mid) add(k*2+1,mid+1,w); tree[k].w=tree[k*2].w+tree[k*2+1].w; }
单点查询代码
void ask(int k)//单点查询 { if(tree[k].l==tree[k].r) { ans=tree[k].w; return ; } if(tree[k].f) pushdown(k);//懒标记下传,唯一需要更改的地方 int m=(tree[k].l+tree[k].r)/2; if(x<=m) ask(k*2); else ask(k*2+1); }
区间查询代码
int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; if (tree[k].f) pushdown(k); int sum=0; if (x<=mid)sum+=ask(k*2,t,mid); if (y>mid)sum+=ask(k*2+1,mid+1,w); tree[k].w=tree[k*2].w+tree[k*2+1].w; return sum; }
更具体的描述 无耻地推荐一下我的博客
所以说此题为线段树的拓展:把求区间和改为了区间最小值,所以只需要将懒标记存储的内容改为最小值,区间修改再加工一下。
部分代码如下:
void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=min(tree[k*2].w,tree[k*2+1].w); } int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; int sum=INT_MAX; if (x<=mid)sum=min(sum,ask(k*2,t,mid)); if (y>mid)sum=min(sum,ask(k*2+1,mid+1,w)); return sum; } int read(int &x) { char c=getchar();int f=1; x=0; while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); } while (c>='0'&&c<='9') { x=x*10+(int)c-48; c=getchar(); } return x*f; }
时间复杂度O(n log n)O(nlogn)
只要不卡常,速度和单调队列和RMQRMQ相差无几,也是一种求最值的方案,可以了解一下。
完整代码:
#include<bits/stdc++.h>
#define N 2000010 using namespace std; struct node{ int l,r,w,f; }tree[N*2+1]; int x,y,z,i,a[N],n,m; void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=min(tree[k*2].w,tree[k*2+1].w); } int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; int sum=INT_MAX; if (x<=mid)sum=min(sum,ask(k*2,t,mid)); if (y>mid)sum=min(sum,ask(k*2+1,mid+1,w)); return sum; } int read(int &x) { char c=getchar();int f=1; x=0; while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); } while (c>='0'&&c<='9') { x=x*10+(int)c-48; c=getchar(); } return x*f; } signed main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(n);m=read(m); memset(a,INT_MAX,sizeof(a)); for (int i=1;i<=n;i++) a[i]=read(a[i]); build(1,1,n); for (int i=1;i<=n;i++) { int c; x=i-m;y=i-1; if (x<=0) x=1; if (x>y) { printf("0\n"); continue; } printf("%d\n",ask(1,1,n)); } }
- 线段树求区间最小
-
1 #include<iostream> 2 #include<cmath> 3 #include<cstdio> 4 using namespace std; 5 struct sb 6 { 7 int l,r,minn; 8 }t[20000010*2]; 9 int read(int &x) 10 { 11 char c=getchar();int f=1; 12 x=0; 13 while (c<'0'||c>'9') 14 { 15 if (c=='-') f=-1; 16 c=getchar(); 17 } 18 while (c>='0'&&c<='9') 19 { 20 x=x*10+(int)c-48; 21 c=getchar(); 22 } 23 return x*f; 24 } 25 void build(int k,int a,int b) 26 { 27 t[k].l=a; t[k].r=b; 28 if(a==b) { t[k].minn=read(t[k].minn); return; } 29 int mid=(a+b)>>1; 30 build(k<<1,a,mid); 31 build(k<<1|1,mid+1,b); 32 t[k].minn=min(t[k<<1|1].minn,t[k<<1].minn); 33 } 34 int find(int k,int a,int b) 35 { 36 if (t[k].l==a&&t[k].r==b) return t[k].minn; 37 int mid=(t[k].l+t[k].r)>>1; 38 if (b<=mid) return find(k<<1,a,b); 39 else if (a>mid) return find(k<<1|1,a,b); 40 else 41 return min(find(k<<1,a,mid),find(k<<1|1,mid+1,b)); 42 } 43 int main () 44 { 45 int n,m; 46 cin>>n>>m; 47 build(1,1,n); 48 cout<<"0"<<endl; 49 for (int i=2;i<=n;i++) 50 printf("%d\n",find(1,max(1,i-m),i-1)); 51 }