题解P8419 [THUPC2022 决赛] riapq
Umbrella_Leaf · · 题解
做的时候找了半天都没有找到详细的题解,只能自己写一个。
题意
给出排列
a_1,a_2,\cdots,a_n ,你需要维护一个初始全0 的序列b 。m 次操作:
1 l r:对于每对l\le i\le j\le r 且a_i\le a_j ,b_j\gets b_j+1 。
2 x:查询b_x 。
题解
对序列以
对于一次修改的贡献,我们拆成
- 左散块对左散块,右散块对右散块,或者
l,r 在同一块; - 整块对整块(包括同一个整块内部);
- 整块对右散块;
- 左散块对右散块;
- 左散块对整块。
其中,“X 对 Y”表示 X 中、Y 中。
接下来分别考虑。
一、左散块对左散块/右散块对右散块/l,r 在同一块
预处理
二、整块对整块
预处理
对每个整块,我们记录前面每一块对它有多少次贡献。
修改:对一段区间内的每个块,左边一段区间内的块对它贡献
询问:扫描
维护贡献次数的差分数组即可。
注意同一块内部的贡献需要特殊处理。
三、整块对右散块
预处理
修改时,对于右散块的每个元素暴力更新答案。
四、左散块对右散块
预处理
使用双指针更新答案,具体不好描述,可浏览代码。
五、左散块对整块
最复杂的部分。
我们建立一个平面,
-
修改
(l,r) :对于左散块每个点i ,在(pos_l+1,a_i) 上+1 ,在(pos_r,a_i) 上-1 。 -
查询
i :查询x 坐标\le pos_i ,y 坐标\le a_i 的点权值和。
转化成
先将
-
修改:
O(n) 次连续在O(\sqrt n) 个位置单点修改。 -
查询:
O(n\sqrt[4]n) 次查询前缀和。
在
对于整块,我们维护前若干块内的和。修改时先差分出每个块内的和,然后暴力修改,最后再做前缀和。
而对于每个散块,我们要做的:
-
修改:
O(n\sqrt n) 次单点修改。 -
查询:
O(n\sqrt[4]n) 次查询前缀和。
貌似没区别?但是序列长度变成了
所以再套一个以
总复杂度
代码
比较丑陋,见谅。
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=x*10+(c^'0'),c=getchar();
return x;
}
#define ll long long
int n,m;
const int B1=460,B2=23;
int a[200005];
int pos[200005],poss[465],L[465],R[465],L1[25],R1[25];
int num[465][200005],prenum[465][200005];
int pnum[465][465][465];
int id[465][465];
int times[465][465];
ll ans[200005];
int xbyb[25][465],xpyb[465][465],xbypyb[25][465][25],xbypyp[25][465][465],xpypyb[465][465][25],xpypyp[465][465][465];
//b 表示 block 块,p 表示 point 单点
bool cmp(int x,int y){return a[x]<a[y];}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
pos[i]=(i-1)/B1+1;
if(!L[pos[i]])L[pos[i]]=i;
R[pos[i]]=i;
}
for(int i=1;i<=B1;i++){
poss[i]=(i-1)/B2+1;
if(!L1[poss[i]])L1[poss[i]]=i;
R1[poss[i]]=i;
}
for(int i=1;i<=pos[n];i++){//预处理
for(int j=L[i];j<=R[i];j++)num[i][a[j]]++;
for(int j=1;j<=n;j++)num[i][j]+=num[i][j-1],prenum[i][j]=num[i][j]+prenum[i-1][j];
for(int j=L[i];j<=R[i];j++)id[i][j-L[i]+1]=j;
sort(id[i]+1,id[i]+R[i]-L[i]+1+1,cmp);
for(int j=1;j<=R[i]-L[i]+1;j++)
for(int k=1;k<=R[i]-L[i]+1;k++)pnum[i][j][k]=pnum[i][j][k-1]+(a[k+L[i]-1]<=a[j+L[i]-1]);
}
while(m--){
int opt=read();
if(opt==1){
int l=read(),r=read();
if(pos[l]==pos[r]){
// l,r 在同一个块
for(int i=l;i<=r;i++)ans[i]+=pnum[pos[l]][i-L[pos[l]]+1][i-L[pos[l]]+1]-pnum[pos[l]][i-L[pos[l]]+1][l-L[pos[l]]];
}else{
// 第一部分 左散块对左散块/右散块对右散块
for(int i=l;i<=R[pos[l]];i++)ans[i]+=pnum[pos[l]][i-L[pos[l]]+1][i-L[pos[l]]+1]-pnum[pos[l]][i-L[pos[l]]+1][l-L[pos[l]]];
for(int i=L[pos[r]];i<=r;i++)ans[i]+=pnum[pos[r]][i-L[pos[r]]+1][i-L[pos[r]]+1];
// 第二部分 整块对整块
for(int i=pos[l]+1;i<pos[r];i++)times[pos[l]+1][i]++;
// 第三部分 整块对右散块
for(int i=L[pos[r]];i<=r;i++)ans[i]+=prenum[pos[r]-1][a[i]]-prenum[pos[l]][a[i]];
// 第四部分 左散块对右散块
for(int i=1,j=0,cnt=0;i<=R[pos[r]]-L[pos[r]]+1;i++){
while(i<=R[pos[r]]-L[pos[r]]+1&&id[pos[r]][i]>r)i++;
if(i>R[pos[r]]-L[pos[r]]+1)continue;
while(j<R[pos[l]]-L[pos[l]]+1&&a[id[pos[l]][j+1]]<=a[id[pos[r]][i]]){
j++;
if(id[pos[l]][j]>=l)cnt++;
}
ans[id[pos[r]][i]]+=cnt;
}
// 第五部分 左散块对整块
int x=pos[l]+1;
// y 整块的部分,差分->暴力修改->前缀和
for(int i=pos[n];i>=1;i--)xbyb[poss[x]][i]-=xbyb[poss[x]][i-1],xpyb[x][i]-=xpyb[x][i-1];
for(int i=l;i<=R[pos[l]];i++)xbyb[poss[x]][pos[a[i]]]++,xpyb[x][pos[a[i]]]++;
for(int i=1;i<=pos[n];i++)xbyb[poss[x]][i]+=xbyb[poss[x]][i-1],xpyb[x][i]+=xpyb[x][i-1];
// y 散块的部分,块内再分块
for(int i=l;i<=R[pos[l]];i++){
int y=a[i];
// x 整块
xbypyb[poss[x]][pos[y]][poss[y-L[pos[y]]+1]]++,xbypyp[poss[x]][pos[y]][y-L[pos[y]]+1]++;
// x 散块
xpypyb[x][pos[y]][poss[y-L[pos[y]]+1]]++,xpypyp[x][pos[y]][y-L[pos[y]]+1]++;
}
// 和上面一样
x=pos[r];
for(int i=pos[n];i>=1;i--)xbyb[poss[x]][i]-=xbyb[poss[x]][i-1],xpyb[x][i]-=xpyb[x][i-1];
for(int i=l;i<=R[pos[l]];i++)xbyb[poss[x]][pos[a[i]]]--,xpyb[x][pos[a[i]]]--;
for(int i=1;i<=pos[n];i++)xbyb[poss[x]][i]+=xbyb[poss[x]][i-1],xpyb[x][i]+=xpyb[x][i-1];
for(int i=l;i<=R[pos[l]];i++){
int y=a[i];
xbypyb[poss[x]][pos[y]][poss[y-L[pos[y]]+1]]--,xbypyp[poss[x]][pos[y]][y-L[pos[y]]+1]--;
xpypyb[x][pos[y]][poss[y-L[pos[y]]+1]]--,xpypyp[x][pos[y]][y-L[pos[y]]+1]--;
}
}
}else{
int x=read(),y=a[x];
ll res=ans[x];// 第一、三、四部分 暴力记录答案
// 第二部分 整块对整块
for(int i=1,s=0;i<=pos[x];i++){
s+=times[i][pos[x]];
if(i<pos[x])res+=1ll*s*num[i][a[x]];
else for(int j=L[i];j<=x;j++)if(a[j]<=a[x])res+=s;
}
// 第五部分 左散块对整块
// x 整块
for(int i=1;i<poss[pos[x]];i++){
// y 整块
res+=xbyb[i][pos[y]-1];
// y 散块内整块
for(int j=1;j<poss[y-L[pos[y]]+1];j++)res+=xbypyb[i][pos[y]][j];
// y 散块内单点
for(int j=L1[poss[y-L[pos[y]]+1]];j<=y-L[pos[y]]+1;j++)res+=xbypyp[i][pos[y]][j];
}
// x 散块(和上面一样)
for(int i=L1[poss[pos[x]]];i<=pos[x];i++){
res+=xpyb[i][pos[y]-1];
for(int j=1;j<poss[y-L[pos[y]]+1];j++)res+=xpypyb[i][pos[y]][j];
for(int j=L1[poss[y-L[pos[y]]+1]];j<=y-L[pos[y]]+1;j++)res+=xpypyp[i][pos[y]][j];
}
printf("%lld\n",res);
}
}
return 0;
}