P6774 [NOI2020] 时代的眼泪 题解
题目描述
给定长为 \(n\) 的排列 \(p_i\) , \(q\) 次询问,求下标在 \([l,r]\) ,值在 \([L,R]\) 的顺序对个数。
数据范围
- \(1\le n\le 10^5,1\le q\le 2\cdot 10^5\) 。
时间限制 \(\texttt{5s}\) ,空间限制 \(\texttt{1GB}\) 。
分析
Warning:如果你没有富裕的空闲时间 & 自信的代码能力 & 足够的耐心,不建议碰这道题。
先对下标分块,那么一个询问区间 [l,r] 被拆成了散块 + 整块 + 散块,如下图:
如果询问的左右端点属于相同块,会在讲散块内部贡献时提到。
否则贡献可以分为如下几类:
- 散块内部。(红\(\to\)红,橙\(\to\)橙)
- 散块对整块,整块对散块。(红\(\to\)绿,绿\(\to\)橙)
- 散块对散块。(红\(\to\)橙)
- 整块内部。(绿\(\to\)绿)
- 整块对整块。(绿\(\to\)另一个绿)
然后各个击破。
散块内部
需要先预处理一点东西。
注意到一个块内只有 \(\mathcal O(\sqrt n)\) 个不同的数,所以先对每个块离散化。
rk[i] 表示 \(i\) 在块内的排名。
pre[i][j]表示块内 \(i\) 的前缀中 \(rk\le j\) 的数的个数。
suf[i][j]表示块内 \(i\) 的后缀中 \(rk\le j\) 的数的个数。
pos[i][j]表示 \(j\) 在第 \(i\) 个块的离散化数组中 lower_bound 的位置,求解时需要双指针扫块内元素和值域。
先求出离散化后的值域区间 [dn,up] ,然后枚举散块所有元素。
以红色区域为例,先判断 rk[i] 是否在值域范围内,贡献为后缀中值域区间 [rk[i]+1,up] 中的元素个数,可以通过 suf 数组求出。橙色区域同理。
如果询问区间左右端点在同一块内呢?
对于每个元素 \(i\) ,先判断是否在值域内,贡献为下标 [i+1,r] ,值域 [rk[i]+1,up] 中的元素个数,在 pre 数组上先对下标差分,然后对值域差分即可。
预处理时间复杂度 \(\mathcal O(n\sqrt n)\) ,单次查询时间复杂度 \(\mathcal O(\sqrt n)\) 。
散块对整块,整块对散块
s[i][j] 表示前 \(i\) 个块中 \(\le j\) 的元素个数。
对于单组询问,记 x=bel[l],y=bel[r] 表示左右端点所在块的编号。
以红色区域为例,枚举散块所有元素,先判断 a[i] 是否在值域内,贡献为下标 [x+1,y-1] ,值域 [a[i]+1,R] 中的元素个数。在 s 数组上两次差分即可。
预处理时间复杂度 \(\mathcal O(n\sqrt n)\) ,单次查询时间复杂度 \(\mathcal O(\sqrt n)\) 。
散块对散块
这个是前置知识那道题的 trick ,预处理时将每个块按升序排序,这样可以 \(\mathcal O(\sqrt n)\) 升序取出两个散块中的元素,然后归并排序。
预处理时间复杂度 \(\mathcal O(n\log n)\) ,单次查询时间复杂度 \(\mathcal O(\sqrt n)\) 。
整块内部
注意每个块的值域只有 \(\mathcal O(\sqrt n)\) ,考虑预处理 f[i][j][k] 表示第 \(i\) 个块内 \(rk\in[j,k]\) 的所有元素的逆序对数。
预处理时枚举 \(j\) ,每次加入 rk=k 的元素,用 pre 数组可以做到 \(\mathcal O(1)\) 更新,时间复杂度 \(\mathcal O(n\sqrt n)\) 。
查询时 \(O(1)\) 回答每个块,总共 \(\mathcal O(\sqrt n)\) 个块,时间复杂度 \(\mathcal O(\sqrt n)\) 。
整块对整块
最难的一部分,做好心理准备。
g[i][j][k] 表示前 \(j\) 个块中,\(\le\) 第 \(i\) 个块中第 \(k\) 大的数的个数,可以通过 pos 和 s 数组 \(\mathcal O(1)\) 求出。
再来考虑询问怎么做。
先对值域进行容斥,钦定下标在 [st[x+1],ed[y-1]] ,答案为 "左端点无限制 、右端点在 [L,R] 的顺序对数" 减去 "左端点在 [1,L-1] 、右端点在 [L,R] 的顺序对数" 。
枚举右端点属于第 \(k\) 个块,假设 [L,R] 在第 \(k\) 个块中离散化后的区间为 [dn,up] 。
第一项枚举右端点的排名 \(i\) ,贡献为 for(int i=dn;i<=up;i++) res+=g[k][k-1][i]-g[k][x][i]; 。对 g 数组按第三维做前缀和即可 \(\mathcal O(1)\) 回答。
第二项很简单,记录第 [x,k-1] 个块中小于 L 的元素个数 cur ,乘上 up-dn+1 即可。
预处理时间复杂度 \(\mathcal O(n\sqrt n)\) ,单次查询需要枚举每个块,时间复杂度 \(\mathcal O(\sqrt n)\)。
完整时间复杂度 \(\mathcal O((n+q)\sqrt n)\) 。
趁晚上交题人少以 \(\texttt{4.96s}\) 跑过去了,不用卡常,感动。
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
#define sum(s,l,r) (s[r]-s[l-1])
using namespace std;
const int maxn=1e5+5,sz=200,b=maxn/sz+5,c=sz+5;
int l,n,q,r,L,R;
int a[maxn],rk[maxn],bel[maxn],cnt[maxn];
int st[b],ed[b],len[b];
int u[c],v[c];
pii p[b][c];
int pre[maxn][c],suf[maxn][c];
int s[b][maxn],pos[b][maxn];
int f[b][c][c];
ll res,g[b][b][c];
inline int read()
{
int q=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=10*q+ch-'0',ch=getchar();
return q;
}
inline void write(ll x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
inline int merge(int *a,int *b,int n,int m)
{
int res=0;
for(int i=1,j=1;j<=m;j++)
{
while(i<=n&&a[i]<=b[j]) i++;
res+=i-1;
}
return res;
}
int main()
{
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read(),bel[i]=(i-1)/sz+1;
for(int i=1;i<=bel[n];i++)
{
#define p p[i]
st[i]=(i-1)*sz+1,ed[i]=min(i*sz,n),len[i]=ed[i]-st[i]+1;
for(int j=st[i];j<=ed[i];j++) p[j-st[i]+1]=mp(a[j],j);
sort(p+1,p+len[i]+1);
for(int j=1;j<=len[i];j++) rk[p[j].se]=j,cnt[p[j].fi]++;
for(int j=st[i];j<=ed[i];j++)
for(int k=1;k<=len[i];k++)
pre[j][k]=(j!=st[i]?pre[j-1][k]:0)+(rk[j]<=k);
for(int j=ed[i];j>=st[i];j--)
for(int k=1;k<=len[i];k++)
suf[j][k]=(j!=ed[i]?suf[j+1][k]:0)+(rk[j]<=k);
for(int j=1,k=1;j<=n+1;j++)
{
while(k<=len[i]&&p[k].fi<j) k++;
s[i][j]=s[i][j-1]+cnt[j],pos[i][j]=k;
}
for(int j=1;j<=len[i];j++)
for(int k=j;k<=len[i];k++)
f[i][j][k]=f[i][j][k-1]+sum(pre[p[k].se],j,k-1);
for(int j=1;j<=i-1;j++)
for(int k=1;k<=len[i];k++)
g[i][j][k]=g[i][j][k-1]+s[j][p[k].fi];
#undef p
}
while(q--)
{
l=read(),r=read(),L=read(),R=read(),res=0;
int x=bel[l],y=bel[r];
if(x==y)
{
int dn=pos[x][L],up=pos[x][R+1]-1;
for(int i=l;i<=r;i++)
if(dn<=rk[i]&&rk[i]<=up)
res+=sum(pre[r],rk[i]+1,up)-sum(pre[i],rk[i]+1,up);
}
else
{
int c0=0,c1=0,dn=pos[x][L],up=pos[x][R+1]-1,cur=0;
for(int i=l;i<=ed[x];i++)
if(dn<=rk[i]&&rk[i]<=up)
{
res+=sum(suf[i],rk[i]+1,up);
res+=sum(s[y-1],a[i]+1,R)-sum(s[x],a[i]+1,R);
}
dn=pos[y][L],up=pos[y][R+1]-1;
for(int i=st[y];i<=r;i++)
if(dn<=rk[i]&&rk[i]<=up)
{
res+=sum(pre[i],dn,rk[i]-1);
res+=sum(s[y-1],L,a[i]-1)-sum(s[x],L,a[i]-1);
}
for(int i=1;i<=len[x];i++)
if(p[x][i].fi>=L&&p[x][i].fi<=R&&p[x][i].se>=l)
u[++c0]=p[x][i].fi;
for(int i=1;i<=len[y];i++)
if(p[y][i].fi>=L&&p[y][i].fi<=R&&p[y][i].se<=r)
v[++c1]=p[y][i].fi;
res+=merge(u,v,c0,c1);
for(int i=x+1;i<=y-1;i++)
{
dn=pos[i][L],up=pos[i][R+1]-1;
res+=f[i][dn][up]+sum(g[i][i-1],dn,up)-sum(g[i][x],dn,up);
res-=1ll*cur*(up-dn+1),cur+=dn-1;
}
}
write(res),putchar('\n');
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16387483.html
浙公网安备 33010602011771号