线段树入门题。
题意很清晰,通过观察屑的定义,可以发现先辈有如下性质:
对于长度为$n$序列$a$,如果它是先辈,那么它满足:
$a_1\le a_2\le a_3 \cdots\le a_n$
为什么呢?通过观察,我们发现先辈的后缀中除了它自己外,它的最长后缀的需要满足的关系最强(即如上性质),而其他后缀需要满足的关系是它的子集,比如第二长的后缀需要满足的关系是$a_1\le a_3, a_2\le a_4 \cdots$,显然更弱,所以说问题转化成了:
- 区间加
- 询问一个区间是否单调上升
然后变成了线段树裸题。
具体做法:线段树维护区间左端点的值$lv$,区间右端点的值$rv$,区间是否是先辈。$pushup$时,如果一段区间的左右儿子都是先辈,并且左儿子的$rv$小于等于右儿子的$lv$,则这段区间是先辈,询问同理。
不要试图抄我的代码,直接交会$wa$一个点
代码(很短的):
inline void update(int u,ll k)
{
tr[u].lv+=k;tr[u].rv+=k;tr[u].la+=k;
}
inline void pushup(Node &u,const Node &a,const Node &b)
{
u.lv=a.lv;u.rv=b.rv;
u.is=a.is&&b.is&&(a.rv<b.lv);
}
inline void pushdown(int u)
{
if(!tr[u].la) return;
update(u<<1,tr[u].la);update(u<<1|1,tr[u].la);
tr[u].la=0;
}
void build(int u,int l,int r)
{
if(l==r) {tr[u].lv=tr[u].rv=a[l];tr[u].is=1;return;}
int mid=(l+r)>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void modify(int u,int l,int r,int x,int y,ll val)
{
if(x<=l&&r<=y) return update(u,val);
int mid=(l+r)>>1;
pushdown(u);
if(x<=mid) modify(u<<1,l,mid,x,y,val);
if(y>mid) modify(u<<1|1,mid+1,r,x,y,val);
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
Node query(int u,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tr[u];
int mid=(l+r)>>1;
pushdown(u);
if(y<=mid) return query(u<<1,l,mid,x,y);
if(x>mid) return query(u<<1|1,mid+1,r,x,y);
Node ans;pushup(ans,query(u<<1,l,mid,x,y),query(u<<1|1,mid+1,r,x,y));
return ans;
}
template<typename T>
inline void read(T &x)
{
char c;int f=1;
while(!isdigit(c=getchar())) (c=='-')&&(f=-1);
x=c^48;
while(isdigit(c=getchar())) x=x*10+(c^48);
x*=f;
}
int main()
{
int t,opt,x,y;
ll val;
read(n);read(t);
for(int i=1;i<=n;++i) read(a[i]);
build(1,1,n);
while(t--)
{
read(opt);read(x);read(y);
if(opt==1) read(val),modify(1,1,n,x,y,val);
else printf("%s\n",query(1,1,n,x,y).is?"Yes":"No");
}
return 0;
}