P8987 [北大集训 2021] 简单数据结构
挺有意思的题。
初步的想法是如果 \(i<j\wedge a_i\leq a_j\) 则在之后的操作中 \(a_i\) 一定仍然不大于 \(a_j\)。
接下来是一个很妙的转化:把每个数看成坐标系中 \(i,a_i\) 的点。正常的取 \(\min\) 操作是对于一条直线 \(y=k\) 取 \(\min\),但是经过了几次 \(2\) 操作后变成了对于一条斜率为“负操作二次数”的直线,纵轴截距为 \(k\) 的直线取 \(\min\),处理到 \(2\) 操作暂时先不管它,少加的部分之后处理。
因为直线斜率单调不增,考虑操作一定是对这个凸包上的一段后缀的真实值做区间推平的操作,这个相对容易处理。
发现一个点如果在凸包上则以后一定还在凸包上,考虑如何计算出每个点变成凸包边界上的点的时间,可以使用整体二分,转为查询当前 \(y-ix\) 的最小值,类似斜率优化,维护 \((x,y)\) 的下凸壳,因为原用来取 \(\min\) 的直线斜率(即当前的 \(x\))和查询的 \(i\)(即当前直线的斜率)都单增,先把全部点都插进去然后指针扫描一下即可。
找出每个点第一次击破时间后问题就很简单了。线段树维护,如果当前操作后某个点从不是凸包上变成了凸包边界,则激活该点,显然全局取 \(\min\) 对于未激活点是无效的,而激活点是单增的,可以线段树上二分然后区间推平。
需要三个标记,推平的值,未激活点操作 \(2\) 次数和激活点操作 \(2\) 次数,信息需要维护激活点下标和,激活点权值和,区间最右侧激活点的下标和权值(线段树上二分要用),区间和,还是有一些细节的。
总复杂度 \(\mathcal O((n +q)\log n)\)。
int n,m,top,len,st[200010],a[200010],ans[200010],pre[200010],Id[200010],id[200010],id1[200010],id2[200010];
tup b[200010];
vector<int> ve[200010];
namespace Segment
{
#define ls(p) (t[p].l+t[p].r)
#define rs(p) ((t[p].l+t[p].r)^1)
struct{int l,r,si,sv,sx,ri,rv,s,tg1,tg2,tg3;}t[400010];
inline void down1(int p,int x)
{
if(!t[p].ri)return;
t[p].tg3=0,t[p].rv=t[p].tg1=x,t[p].s-=t[p].sv;
t[p].sv=t[p].si*x,t[p].s+=t[p].sv;
}
inline void down2(int p,int x)
{
t[p].s+=x*((t[p].l+t[p].r)*(t[p].r-t[p].l+1)/2-t[p].sx);
t[p].tg2+=x;
}
inline void down3(int p,int x)
{
if(!t[p].ri)return;
t[p].s+=t[p].sx*x,t[p].sv+=t[p].sx*x;
t[p].rv+=t[p].ri*x,t[p].tg3+=x;
}
inline void spread(int p)
{
if(~t[p].tg1)down1(ls(p),t[p].tg1),down1(rs(p),t[p].tg1),t[p].tg1=-1;
if(t[p].tg2)down2(ls(p),t[p].tg2),down2(rs(p),t[p].tg2),t[p].tg2=0;
if(t[p].tg3)down3(ls(p),t[p].tg3),down3(rs(p),t[p].tg3),t[p].tg3=0;
}
inline void update(int p)
{
assert(t[p].tg1==-1),assert(!t[p].tg2),assert(!t[p].tg3);
t[p].ri=t[ls(p)].ri,t[p].rv=t[ls(p)].rv;
if(t[rs(p)].ri)t[p].ri=t[rs(p)].ri,t[p].rv=t[rs(p)].rv;
t[p].s=t[ls(p)].s+t[rs(p)].s,t[p].sv=t[ls(p)].sv+t[rs(p)].sv;
t[p].si=t[ls(p)].si+t[rs(p)].si,t[p].sx=t[ls(p)].sx+t[rs(p)].sx;;
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r,t[p].tg1=-1;
if(l==r)return t[p].s=a[l],void();
int mid=l+((r-l)>>1);
build(ls(p),l,mid),build(rs(p),mid+1,r),update(p);
}
void change(int p,int l,int x)
{
if(l<=t[p].l)return down1(p,x);
spread(p),change(rs(p),l,x);
if(l<=t[ls(p)].r)change(ls(p),l,x);
update(p);
}
void active(int p,int x,int y)
{
if(t[p].l==t[p].r)
{
t[p].sv=t[p].s=y,t[p].si=1;
t[p].sx=t[p].ri=t[p].l,t[p].rv=y;
return;
}
spread(p);
if(x<=t[ls(p)].r)active(ls(p),x,y);
else active(rs(p),x,y);
update(p);
}
int find(int p,int v)
{
if(!t[p].ri)return inf;
if(t[p].l==t[p].r)return t[p].rv>=v?t[p].l:inf;
spread(p);
if(t[ls(p)].rv<=v)return find(rs(p),v);
return find(ls(p),v);
}
int ask(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r)return t[p].s;
spread(p);
if(r<=t[ls(p)].r)return ask(ls(p),l,r);
if(l>t[ls(p)].r)return ask(rs(p),l,r);
return ask(ls(p),l,r)+ask(rs(p),l,r);
}
void print(int x)
{
if(t[x].l==t[x].r)return write(t[x].s);
spread(x);
print(ls(x)),print(rs(x));
}
}
using namespace Segment;
#define dy(i,j) (b[j].y-b[i].y)
#define dx(i,j) (pre[j]-pre[i]+eps)
inline void ins(int x)
{
while(top>1&&(db)(dy(st[top],x)*dx(st[top-1],st[top]))<=(db)(dy(st[top-1],st[top])*dx(st[top],x)))--top;
st[++top]=x;
}
void solve(int L,int R,int l,int r)
{
if(l>r)return;
if(L==R){for(int i=l;i<=r;++i)ans[Id[i]]=L;return;}
int mid=L+((R-L)>>1),len1=0,len2=0;top=0;
for(int i=L;i<=mid;++i)ins(id[i]);
for(int i=l,j=1;i<=r;++i)
{
while(j<top&&(db)(dy(st[j],st[j+1]))<=(db)(Id[i]*dx(st[j],st[j+1])))++j;
int v=b[st[j]].y-pre[st[j]]*Id[i];
if(v<=a[Id[i]])id1[++len1]=Id[i];
else id2[++len2]=Id[i];
}
for(int i=1;i<=len1;++i)Id[i+l-1]=id1[i];
for(int i=1;i<=len2;++i)Id[i+l+len1-1]=id2[i];
solve(L,mid,l,l+len1-1),solve(mid+1,R,l+len1,r);
}
inline void mian()
{
read(n,m);int opt,x;
for(int i=1;i<=n;++i)read(a[i]),Id[i]=i;
for(int i=1,minn=INF;i<=m;++i)
{
read(opt),pre[i]=pre[i-1]+(opt==2);
if(opt==1)
{
read(x),b[i]=tup(opt,x,0);
if(x>=minn){--m,--i;continue;}
minn=x,id[++len]=i;
}
else if(opt==3)read(b[i].y,b[i].z),b[i].x=3;
else minn=INF,b[i]=tup(opt,0,0);
}
solve(1,len+1,1,n),build(1,1,n);
for(int i=1;i<=n;++i)if(ans[i]<=len)ve[id[ans[i]]].eb(i);
for(int i=1;i<=m;++i)
{
if(b[i].x==1)
{
x=find(1,b[i].y);
x<inf?change(1,x,b[i].y),0:0;
}
else if(b[i].x==2)down2(1,1),down3(1,1);
else write(ask(1,b[i].y,b[i].z),'\n');
for(auto j:ve[i])active(1,j,b[i].y);
}
}