P6578 [Ynoi2019] 魔法少女网站 题解
题目描述
给定长为 \(n\) 的序列, \(m\) 次操作:
1 x y:将 \(a_x\) 修改为 \(y\) 。2 l r x:求 \(\sum\limits_{l\le l'\le r'\le r}[\max\limits_{l'\le i\le r'} a_i\le x]\) 。
数据范围
- \(1\le n,m\le 3\cdot 10^5\) 。
- \(1\le l\le r\le n,1\le a_i,x,y\le n\) 。
时间限制 \(\texttt{3.5s}\) ,空间限制 \(\texttt{64MB}\) 。
分析
询问的答案为对 \([l,r]\) 中所有 \(\le x\) 的数构成的连续段, \(\frac{len(len+1)}2\) 之和。
先考虑没有修改时怎么做。
将询问按 \(x\) 升序排序,这样总共有 \(n\) 次单点插入操作,每次操作可能引起连续段合并。
序列分块,记块长为 \(B_1\) 。
维护 pre,suf,sum 数组分别表示 \(i\) 为段尾时段头的位置(不跨块)、 \(i\) 为段头时段尾的位置(不跨块)、第 \(i\) 块内连续段的贡献和。
当 \(i\) 不为段尾时,令
pre[i]=0,suf同理。
显然合并代价 \(\mathcal O(1)\) ,回答询问时,维护当前答案 ans 和最后一个连续段长度 cur ,散块暴力扫,整块将 cur 和第一个连续段合并,然后用最后一个连续段更新 cur 。
预处理 \(\mathcal O(n)\) ,单组询问 \(\mathcal O(B_1+\frac n{B_1})\) 。
ll ans=0,cur=0;
auto work=[&](int j)
{
a[j]<=x?ans+=++cur:cur=0;
};
if(u==v) for(int j=l;j<=r;j++) work(j);
else
{
for(int j=l;j<=ed[u];j++) work(j);
for(int j=u+1;j<=v-1;j++)
{
ans+=cur*max(suf[st[j]]-st[j]+1,0)+sum[j];
if(!pre[ed[j]]) cur=0;
else if(pre[ed[j]]==st[j]) cur+=B1;
else cur=ed[j]-pre[ed[j]]+1;
}
for(int j=st[v];j<=r;j++) work(j);
}
接下来考虑修改操作。
询问依然按照 \(x\) 升序排序,将修改后的数组看成新建一个版本,我们需要一个指针滚动版本编号。
操作分块,每次处理 \(B_2\) 个操作,处理完记得更新 \(a\) 数组。
我们可以用 \(\mathcal O(n+B_2^2)\) 的时空复杂度维护所有版本。
具体的,将 \(t\le B_2\) 个修改的位置挑出来,从上一版本 memcpy 得到当前版本然后执行修改操作,其余 \(n-t\) 个位置的值始终不变。
再考虑如何回答询问。
合并连续段(插入)很好维护,但是修改会引入分裂连续段(删除)的操作,删除操作维护起来非常困难。
类比回滚莫队,我们希望将删除操作转化为插入 + 撤销。
非修改位置只会被插入一次,用 vector 记录插入时机。
网上很多题解这一步建议用邻接表,但实际上邻接表的
cache miss非常大,实际效率反而不如vector。这里需要用 \(B_2\) 个
vector合计存储 \(\mathcal O(n)\) 个数,注意clear函数会删除元素但不会释放空间,清空应该写成vector<int>().swap(vec[i]);,否则 \(\frac m{B_1}\) 轮操作会导致总空间非常大,出现一些莫名其妙的 \(\texttt{MLE}\) 。
回答询问前扫描所有修改位置,如果当前版本的 \(a_i\) 不超过 \(x\) ,插入位置 \(i\) ,回答完再撤销所有修改位置的贡献。
时间复杂度 \(\mathcal O(\frac m{B_2}\cdot(n+B_2^2)+m(B_1+\frac n{B_1}))=\mathcal O(m\sqrt n)\) 。
卡常 Tips :
fread快读,本题 \(1.5\cdot 10^6\) 的读入量,相比普通快读可以节约 \(0.3\sim 0.5\texttt{s}\) 的时间。- 块长 \(B_1\) 取 \(500\) 左右, \(B_2\) 取 \(1500\) 左右最优。
- 序列分块的部分需要精细实现,用三个数组(
pre,suf,sum)实现比用 \(\frac n{B_1}\) 个结构体快的多。- 撤销部分需要精细实现,笔者最开始记录每个修改的位置和修改前的值,但其中有大量冗余信息,最后也不出意料的 \(\texttt{TLE}\) 了。实际上,记录被修改位置 \(x\) ,合并后的左右端点 \(l,r\) ,修改前 \(x\) 所在连续段的贡献
sum[bel[x]]就可以还原出所有信息。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int B1=480,B2=1500,maxn=3e5+5;
int m,n,flg,top;/// flg=0/1 表示不需要/需要被撤销
int a[maxn],x[maxn],y[maxn],id[maxn],buc[maxn];
int bel[maxn],st[maxn/B1+5],ed[maxn/B1+5];
int b[B2+5][B2+5];
int pre[maxn],suf[maxn];///对连续段结尾, pre 存储开头;对连续段开头, suf 存储结尾
ll res[B2+5],sum[maxn/B1+5];/// sum[i] 表示第 i 块的贡献和
vector<int> vec[B2+5];
struct quer
{
int l,r,x,t,id;
}e[B2+5];
struct oper
{
int l,r,x;
ll s;
}sta[B2+5];
inline ll calc(const int &x)
{
return x*(x+1ll)/2;
}
inline void modify(const int &x)
{
///栈存储 int 类型修改,将 sum 的高低位分别存储
int l=x,r=x,u=bel[x];
ll tmp=sum[u];
if(x!=st[u]&&pre[x-1]) sum[u]-=calc(x-pre[x-1]),l=pre[x-1],pre[x-1]=0;
if(x!=ed[u]&&suf[x+1]) sum[u]-=calc(suf[x+1]-x),r=suf[x+1],suf[x+1]=0;
sum[u]+=calc(r-l+1),pre[r]=l,suf[l]=r;
if(flg) sta[++top]={l,r,x,tmp};
}
int read()
{
int q=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) q=q*10+ch-'0',ch=getchar();
return q;
}
void roll()
{
while(top)
{
int l=sta[top].l,r=sta[top].r,x=sta[top].x;
sum[bel[x]]=sta[top--].s,pre[x]=suf[x]=0;
if(l!=x) pre[x-1]=l,suf[l]=x-1;
if(r!=x) suf[x+1]=r,pre[r]=x+1;
}
}
void solve(int m)
{
vector<int> h;
int k=0,t=0;
for(int i=1;i<=m;i++)
{
int op=read();
if(op==1) x[++t]=read(),y[t]=read(),h.push_back(x[t]);
else e[++k].l=read(),e[k].r=read(),e[k].x=read(),e[k].t=t,e[k].id=k;
}
///修改位置分配唯一编号,其余位置编号 -1, b[i] 存储第 i 次修改后的值
sort(h.begin(),h.end());
h.erase(unique(h.begin(),h.end()),h.end());
memset(id,-1,sizeof(id));
for(int i=0;i<h.size();i++) id[h[i]]=i,b[0][i]=a[h[i]];
for(int i=1;i<=t;i++) memcpy(b[i],b[i-1],4*h.size()),b[i][id[x[i]]]=y[i];
///询问按 x 升序排序, vec[i] 存储回答第 i 个询问前 0->1 的非修改位置
sort(e+1,e+k+1,[](quer a,quer b){return a.x<b.x;});
for(int i=1;i<=k;i++) for(int j=e[i-1].x+1;j<=e[i].x;j++) buc[j]=i;
fill(buc+e[k].x+1,buc+n+1,0);
for(int i=1;i<=n;i++) if(!~id[i]&&buc[a[i]]) vec[buc[a[i]]].push_back(i);
memset(pre,0,sizeof(pre));
memset(suf,0,sizeof(suf));
memset(sum,0,sizeof(sum));
for(int i=1;i<=k;i++)
{
int l=e[i].l,r=e[i].r,x=e[i].x,t=e[i].t,u=bel[l],v=bel[r];
for(auto j:vec[i]) modify(j);///非修改位置
flg=1;
for(int i=0;i<h.size();i++) if(b[t][i]<=x) modify(h[i]);///修改位置,后面会撤销
ll ans=0,cur=0;
auto work=[&](int i)
{
if((~id[i]?b[t][id[i]]:a[i])<=x) ans+=++cur;
else cur=0;
};
if(u==v) for(int j=l;j<=r;j++) work(j);
else
{
for(int j=l;j<=ed[u];j++) work(j);
for(int j=u+1;j<=v-1;j++)
{
ans+=cur*max(suf[st[j]]-st[j]+1,0)+sum[j];
if(!pre[ed[j]]) cur=0;
else if(pre[ed[j]]==st[j]) cur+=B1;
else cur=ed[j]-pre[ed[j]]+1;
}
for(int j=st[v];j<=r;j++) work(j);
}
res[e[i].id]=ans,flg=0,roll();
}
///清空需要精细实现
for(int i=1;i<=k;i++) printf("%lld\n",res[i]),vector<int>().swap(vec[i]);
for(int i=0;i<h.size();i++) a[h[i]]=b[t][i];
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read(),bel[i]=(i-1)/B1+1;
for(int i=1;i<=bel[n];i++) st[i]=(i-1)*B1+1,ed[i]=min(i*B1,n);
for(int i=1;i<=m/B2;i++) solve(B2);
solve(m%B2);
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/19601797
浙公网安备 33010602011771号