习题:V(线段树)
Posted loney-s
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了习题:V(线段树)相关的知识,希望对你有一定的参考价值。
题目
思路
涉及到区间操作并且是静态的区间,用线段树是再合适不过的
如果直接维护每个点的权值不麻烦
但是要维护历史最大值就十分麻烦
所以我们转化下思路
线段树上维护操作
接着我们思考如何将操作统一化,并且是可叠加的,这样才能方便用懒标记
我们设标记 ((a,b))表示将x变为 (max(x+a,b))
区间加:((a,-INF))
区间赋值:((-INT,a))
将每个值变为(max(x_i-a,0))即为:((-a,0))
接着我们考虑合并的问题
假设现在的标记为((a,b)),从父亲节点传下来的标记为((c,d))
先将第一个标记表示出来(max(x+a,b)),这就是现在的(x'),
再将(x') 代入
现在即为(max(c+max(x+a,b),d))
可以发现对于前一项的b,b与x无关
而我们定义标记的前一项是与x有关,而后一项与x无关
所以可以将其提取出来,即为
(max(c+a+x,max(c+b,d)))
所以合并之后的标记即为
((a+c,max(b+c),d))
现在已经解决了标记合并的问题
现在我们来考虑最大值的维护
如果我们将标记看成一个函数,
那么这个函数的图像一定是这样的:
也就意味这合并之后的标记也是这样子的
同时注意到前面一段的平板是由标记的第二项决定的
后面一段上升的直线是由第一项决定的
当 (x_1<x_2),必然有(f(x_1)le f(x_2))
所以我们可以直接将现在的标记怼上去就行了
什么意思呢?
如果现在的最大值函数为(f(x))
当前的操作为(g(x))
那么新的最大值函数
(h(x)=egin{cases}f(x)[f(x)>g(x)]g(x)[其他情况]end{cases})
你看h很复杂,其实不然,如果你将两个函数转换成图像的形式之后
你就发现(h(x))的图像跟标记的函数图像一模一样
所以你就可以对标记的第一项取一个max,对后一项取一个max就行了
更新的时候最好写一个取值的边界的判断条件
笔者因此调了一个上午
别忘了开读入优化,UOJ对cin,cout不太友好,即使关了同步
代码
#include<iostream>
#include<cstdio>
using namespace std;
#define pll pair<long long,long long>
#define x first
#define y second
#define basic make_pair(0,0);
struct node
{
int l;
int r;
pll val;
pll maxx;
}tre[2000005];
int n,m;
int a[500005];
int opt,l,r,x;
void read(int &x)
{
x=0;
int f=1;
char c=getchar();
while('0'>c||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while('0'<=c&&c<='9')
{
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
x*=f;
}
void write(long long x)
{
if(x>9)
write(x/10);
putchar(x%10+'0');
}
pll operator + (const pll &a,const pll &b)
{
pll t;
t.x=a.x+b.x;
t.y=a.y+b.y;
return t;
}
pll update_pair(pll a,pll b)
{
pll t;
t.x=a.x+b.x;
t.y=max(a.y+b.x,b.y);
return t;
}
void push_down(int k,int fa)
{
pll t1=update_pair(tre[k].val,tre[fa].val);
pll t2=update_pair(tre[k].val,tre[fa].maxx);
tre[k].val.x=max(t1.x,-(1ll<<55));
tre[k].val.y=t1.y;
tre[k].maxx.x=max(tre[k].maxx.x,t2.x);
tre[k].maxx.y=max(tre[k].maxx.y,t2.y);
}
void init(int k)
{
tre[k].val.x=tre[k].maxx.x=0;
tre[k].val.y=tre[k].maxx.y=-(1ll<<55);
}
void build(int l,int r,int k)
{
tre[k].l=l;
tre[k].r=r;
tre[k].val=basic;
tre[k].maxx=basic;
if(l==r)
return;
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
}
void add(int l,int r,int k,pll a)
{
if(tre[k].l>r||tre[k].r<l)
return;
if(l<=tre[k].l&&tre[k].r<=r)
{
pll t1=update_pair(tre[k].val,a);
tre[k].val=t1;
tre[k].maxx.x=max(tre[k].maxx.x,t1.x);
tre[k].maxx.y=max(tre[k].maxx.y,t1.y);
return;
}
push_down(k<<1,k);
push_down(k<<1|1,k);
init(k);
add(l,r,k<<1,a);
add(l,r,k<<1|1,a);
}
pll ask_now(int u,int k)
{
if(tre[k].l>u||tre[k].r<u)
return basic;
if(tre[k].l==tre[k].r)
return tre[k].val;
push_down(k<<1,k);
push_down(k<<1|1,k);
init(k);
return ask_now(u,k<<1)+ask_now(u,k<<1|1);
}
pll ask_maxx(int u,int k)
{
if(tre[k].l>u||tre[k].r<u)
return basic;
if(tre[k].l==tre[k].r)
return tre[k].maxx;
push_down(k<<1,k);
push_down(k<<1|1,k);
init(k);
return ask_maxx(u,k<<1)+ask_maxx(u,k<<1|1);
}
signed main()
{
read(n);
read(m);
for(int i=1;i<=n;i++)
read(a[i]);
build(1,n,1);
for(int i=1;i<=m;i++)
{
read(opt);
if(opt<=3)
{
read(l);
read(r);
read(x);
if(opt==1)
{
add(l,r,1,make_pair(x,-(1ll<<55)));
}
if(opt==2)
{
add(l,r,1,make_pair(-x,0));
}
if(opt==3)
{
add(l,r,1,make_pair(-(1ll<<55),x));
}
}
else
{
read(x);
if(opt==4)
{
pll t=ask_now(x,1);
write(max(a[x]+t.x,t.y));
putchar('
');
}
if(opt==5)
{
pll t=ask_maxx(x,1);
write(max(a[x]+t.x,t.y));
putchar('
');
}
}
}
return 0;
}
以上是关于习题:V(线段树)的主要内容,如果未能解决你的问题,请参考以下文章