E题补题题解(AI润色)
我们需要高效处理两种核心操作:对数组a
的区间更新(批量增加)和对数组p
的区间查询(计算p
的区间内对应a
中元素的总和)。由于数据规模达1e5
,直接暴力处理会超时,因此采用分块算法平衡效率:将数组分为若干块,对完整块用“懒标记”批量处理,对非完整块直接暴力操作,同时通过预处理贡献关系快速维护查询所需的总和。
分块基本设置
- 核心数组:
a
:长度为n
的初始全0数组,支持区间增加。p
:长度为n
的映射数组,p[i]
表示查询时需访问a[p[i]]
。
- 分块设计:
- 将
a
和p
均按块长len
分为cnt
块,块编号从1到cnt
。 lazy[i]
:a
的第i
块的“懒标记”,记录该块整体需要增加的值(用于完整块的批量更新)。sum[i]
:p
的第i
块的总和(即p
的第i
块中所有a[p[j]]
的和,动态维护)。
- 将
预处理:贡献关系与前缀和
由于p
到a
的映射固定,可提前预处理“更新对查询的贡献”,避免每次更新时重复计算:
-
块对块贡献
f[i][j]
:
定义:当a
的第i
块整体加1时,p
的第j
块的总和sum[j]
增加的量(即p
的第j
块中,p[k]
属于a
的第i
块的元素个数)。 -
ff[i][j]:
定义:当a
的第i
个元素加1时,p
的第j
块的总和sum[j]
增加的量(即p
的第j
块中,p[k] = i
的元素个数)。 -
前缀和处理:
对f
和ff
在列方向(块编号)上做前缀和,得到:f_prefix[i][j]
:a
的第1到i
块整体加1时,对p
的第j
块的总贡献(f_prefix[i][j] = f[1][j] + ... + f[i][j]
)。ff_prefix[i][j]
:a
的第1到i
个元素加1时,对p
的第j
块的总贡献(ff_prefix[i][j] = ff[1][j] + ... + ff[i][j]
)。
作用:通过前缀和,a
的区间[l, r]
更新对sum[j]
的贡献可在O(1)
时间内计算(如a
的[l, r]
加x
,对sum[j]
的贡献为(ff_prefix[r][j] - ff_prefix[l-1][j]) * x
)。
一、区间更新a
的操作
对a
的[l, r]
区间加x
时,需分情况处理,并同步更新sum
(p
的块总和):
-
完整块处理(
a
的块[L+1, R-1]
):- 用
lazy
标记批量更新:lazy[i] += x
(i
为a
的完整块编号)。 - 同步更新
sum
:利用f_prefix
计算贡献,对p
的所有块j
,sum[j] += (f_prefix[R-1][j] - f_prefix[L][j]) * x
。
- 用
-
非完整块处理(
a
的[l, rx[L]]
和[lx[R], r]
,rx[L]
为a
的第L
块右边界,lx[R]
为第R
块左边界):- 暴力更新
a
的元素:a[i] += x
(i
为非完整块内的元素)。 - 同步更新
sum
:利用ff_prefix
计算贡献,对p
的所有块j
,sum[j] += (ff_prefix[rx[L]][j] - ff_prefix[l-1][j] + ff_prefix[r][j] - ff_prefix[lx[R]-1][j]) * x
。
- 暴力更新
二、查询操作(计算p
的[l, r]
区间总和)
查询时需结合sum
(完整块的预计算总和)和暴力计算(非完整块的单点值):
-
完整块查询(
p
的[L+1, R-1]
块):
直接累加sum[i]
(i
为p
的完整块编号)。 -
非完整块查询(
p
的[l, rx[L]]
和[lx[R], r]
):
暴力计算每个元素:a[p[i]] + lazy[pos[p[i]]]
(pos[p[i]]
为p[i]
在a
中所属的块编号,lazy
用于叠加该块的批量更新值)。
为什么块长不取根号n? 因为此题卡内存限制,需要用时间换空间,1e5数据量n^(5/3)也能通过。
#include<bits/stdc++.h>
using namespace std;
//const int mod=998244353;
//const int inv2=mod-mod/2;
//#define int __int128
//#define double long double
#define int long long
#define x first
#define y second
using u16=unsigned short;
using u32=unsigned int;
using u64=unsigned long long;
using pii=pair<int,int>;
using pipii=pair<int,pii>;
using piipi=pair<pii,int>;
using piipii=pair<pii,pii>;
using vi=vector<int>;
using vc=vector<char>;
using vpii=vector<pii>;
using vvi=vector<vector<int>>;
using vvc=vector<vector<char>>;
void solve()
{
int n,m;cin>>n>>m;
vi a(n+1),p(n+1),h[n+1];
for(int i=1;i<=n;i++)
{
cin>>p[i];
h[p[i]].push_back(i);//反向映射
}
int len=pow(n,2.0/3.0);//块长
int cnt=(n+len-1)/len;//块数
vi lx(cnt+1),rx(cnt+1),pos(n+1),lazy(cnt+1),sum(cnt+1);
for(int i=1;i<=n;i++)pos[i]=(i+len-1)/len;
for(int i=1;i<=cnt;i++)lx[i]=(i-1)*len+1,rx[i]=min(n,i*len);
vvi f(cnt+1,vi(cnt+1));//O(n*sqrt(n))复杂度预处理f[i][j]:a的第i块整体+1时对p的第j块整体的贡献
for(int i=1;i<=cnt;i++)
for(int j=1;j<=cnt;j++)
for(int k=lx[j];k<=rx[j];k++)
if(p[k]>=lx[i] && p[k]<=rx[i])f[i][j]++;
vvi ff(n+1,vi(cnt+1));//O(n)复杂度预处理ff[i][j]:a的第i个数+1时对p的第j块整体的贡献
for(int i=1;i<=n;i++)
for(int j:h[i])ff[i][pos[j]]++;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=cnt;j++)f[i][j]+=f[i-1][j];//列方向前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=cnt;j++)ff[i][j]+=ff[i-1][j];//列方向前缀和
int pre_ans=0;
while(m--)
{
int op;cin>>op;
if(op==1)//区间加
{
int l,r,x;cin>>l>>r>>x;
l^=pre_ans,r^=pre_ans,x^=pre_ans;
int L=pos[l],R=pos[r];
if(L==R)//同一块中
{
for(int i=l;i<=r;i++)a[i]+=x;
for(int j=1;j<=cnt;j++)sum[j]+=(ff[r][j]-ff[l-1][j])*x;
}
else
{
for(int i=l;i<=rx[L];i++)a[i]+=x;
for(int i=lx[R];i<=r;i++)a[i]+=x;
for(int j=1;j<=cnt;j++)sum[j]+=(ff[rx[L]][j]-ff[l-1][j]+ff[r][j]-ff[lx[R]-1][j])*x;//点对块更新
for(int i=L+1;i<=R-1;i++)lazy[i]+=x;
for(int j=1;j<=cnt;j++)sum[j]+=(f[R-1][j]-f[L][j])*x;//块对块更新
}
}
else//区间查询
{
int l,r;cin>>l>>r;
l^=pre_ans,r^=pre_ans;
int L=pos[l],R=pos[r],ans=0;
if(L==R)for(int i=l;i<=r;i++)ans+=a[p[i]]+lazy[pos[p[i]]];//同一块中
else
{
for(int i=l;i<=rx[L];i++)ans+=a[p[i]]+lazy[pos[p[i]]];
for(int i=lx[R];i<=r;i++)ans+=a[p[i]]+lazy[pos[p[i]]];
for(int i=L+1;i<=R-1;i++)ans+=sum[i];
}
cout<<ans<<'\n';
pre_ans=ans;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}