题目大意
有n个单调栈(单调递减)排成一排,一开始每个栈都是空的。
有
1≤n,q≤2×105,1≤x≤109
题目分析
考虑离线,把所有操作挂在对应端点上,然后从左向右扫描线。
以时间为下标建立线段树,考虑动态地维护当前扫到的单调栈在每一时间点的答案。
这个就十分套路了,和WC2013的楼房重建差不多。
先考虑如何查询,定义函数query([l,r],p)表示查询时间区间[l,r]组成的单调栈在最后加入p之后中所有数的和,查询时显然
如果l=r,那么我们可以直接计算答案。
设mid=⌊l+r2⌋,rmx表示区间[mid+1,r]中所有数的最大值。
如果rmx>p,那么显然p不会对左半区间产生任何影响,而右边的最大值会加入到左边区间的单调栈中,答案等于
否则,右边的单调栈会直接被p全部弹出,答案等于
如果我们一直这样递归下去,复杂度是没有保证的,怎么办呢?
令fx表示x这个节点右半边区间最大值加入到左半边的单调栈之后,单调栈的所有数只和,即
假如我们的query操作中的区间已经是一个完整的线段树区间了,那么我们的rmx显然可以O(1)得到,query([l,mid],rmx)其实就是fx,也可以O(1)得到。
那么这个query的时间复杂度如何分析呢?
我们研究里面的嵌套函数调用了多少次。
首先,在我的区间还不是一个完整的线段树区间时,rmx需要通过调用区间最小值来得到,调用一次的复杂度是O(logq)的,而调用次数也是O(logq)的,因此这个复杂度是O(log2q)的。
其次,我们的查询操作会下放到O(logq)个完整的线段树区间,观察发现接下来我们往下走的过程已经不会增加新的分支,都是一条路走到底,因此每个区间最多会向下走O(logq)步,这个时间复杂度也是O(log2q)的。
至于fx怎么在每次修改之后重新得到?直接在update时调用一下query就好了。每次修改会调用O(logq)次update,由于这时我的query都是在线段树上的完整区间上查,因此每次复杂度是O(logq)的。
于是总的时间复杂度就是O(qlog2q)的。这个套路真的很精彩。
代码实现
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
typedef long long LL;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int buf[30];
void write(LL x)
{
if (x<0) putchar('-'),x=-x;
for (;x;x/=10) buf[++buf[0]]=x%10;
if (!buf[0]) buf[++buf[0]]=0;
for (;buf[0];putchar('0'+buf[buf[0]--]));
}
const int Q=200050;
const int M=Q<<1;
const int S=Q<<2;
struct query
{
int opt,x,ti,val;
query (int opt_=0,int x_=0,int ti_=0,int val_=0){opt=opt_,x=x_,ti=ti_,val=val_;}
bool operator<(query const qry)const{return x<qry.x||x==qry.x&&opt<qry.opt;}
}qy[M];
inline int max(int x,int y){return x>y?x:y;}
struct segment_tree
{
int mx[S];
LL f[S];
int querymax(int x,int st,int en,int l,int r)
{
if (st==l&&en==r) return mx[x];
int mid=l+r>>1;
if (en<=mid) return querymax(x<<1,st,en,l,mid);
else if (mid+1<=st) return querymax(x<<1|1,st,en,mid+1,r);
else return max(querymax(x<<1,st,mid,l,mid),querymax(x<<1|1,mid+1,en,mid+1,r));
}
LL querysum(int x,int st,int en,int l,int r,int p)
{
if (l==r) return (mx[x]>p)*mx[x]+p;
int mid=l+r>>1;bool cvr=st==l&&en==r;
if (en<=mid) return querysum(x<<1,st,en,l,mid,p);
else if (mid+1<=st) return querysum(x<<1|1,st,en,mid+1,r,p);
else
{
int rmx=cvr?mx[x<<1|1]:querymax(x<<1|1,mid+1,en,mid+1,r);
if (rmx>p) return (cvr?f[x]:querysum(x<<1,st,mid,l,mid,rmx))+querysum(x<<1|1,mid+1,en,mid+1,r,p)-rmx;
else return querysum(x<<1,st,mid,l,mid,p);
}
}
void update(int x,int l,int r)
{
mx[x]=max(mx[x<<1],mx[x<<1|1]);
int mid=l+r>>1;
f[x]=querysum(x<<1,l,mid,l,mid,mx[x<<1|1]);
}
void modify(int x,int y,int l,int r,int val)
{
if (l==r)
{
mx[x]=val;
return;
}
int mid=l+r>>1;
if (y<=mid) modify(x<<1,y,l,mid,val);
else modify(x<<1|1,y,mid+1,r,val);
update(x,l,r);
}
}t;
int n,q,cnt,qid;
LL ans[Q];
void calc()
{
sort(qy+1,qy+1+cnt);
for (int cur=1,ptr;cur<=cnt;)
{
for (ptr=cur;cur<=cnt&&qy[cur].x==qy[ptr].x&&!qy[cur].opt;++cur) t.modify(1,qy[cur].ti,1,q,qy[cur].val);
for (;cur<=cnt&&qy[cur].x==qy[ptr].x&&qy[cur].opt==1;++cur) ans[qy[cur].val]=t.querysum(1,1,qy[cur].ti,1,q,0);
for (;cur<=cnt&&qy[cur].x==qy[ptr].x;++cur) t.modify(1,qy[cur].ti,1,q,0);
}
}
int main()
{
freopen("stack.in","r",stdin),freopen("stack.out","w",stdout);
n=read(),q=read();
for (int i=1;i<=q;++i)
{
int opt=read();
if (opt==1)
{
int l=read(),r=read(),x=read();
qy[++cnt]=query(0,l,i,x),qy[++cnt]=query(2,r,i,0);
}
else
{
int x=read();
qy[++cnt]=query(1,x,i,++qid);
}
}
calc();
for (int i=1;i<=qid;++i) write(ans[i]),putchar('\n');
fclose(stdin),fclose(stdout);
return 0;
}