线段树
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 }
为何要逼自己长大,去闯不该闯的荒唐