【洛谷6774】[NOI2020]时代的眼泪(分块“入门”题)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
【洛⾕6774】[NOI2020]时代的眼泪(分块“⼊门”题)
给定⼀个长度为n的序列a,每次询问[l,r]区间内值在[L,R]范围内的所有元素中存在多少顺序对。
n≤105,m≤2×105
我觉得我就是“时代的眼泪”,卡常卡到⼼态爆炸。
可以先看⼀看这道题的弱化版:(已经是道⿊题了)。
听说这是道分块“⼊门”题,仔细想了想感觉思维难度的确不是很⾼?
序列分块:处理散块
⾸先我们考虑序列分块,那么就是要把⼀个询问区间拆成散块+若⼲整块+散块。
然后注意到⼀个重要性质,⼀个块中数的种数是O(√n)的,所以我们可以对每个块事先离散化,预处理出T i,x表⽰第i个块中⼤于等于x的最⼩的数的离散化后的标号,并记录第i个数a i在离散化后变成了p i。
借助这个性质我们就可以预先求出pre i,v,suf i,v分别表⽰i在所在块内的前缀/后缀中,离散化后值⼩于等于v的数的个数。
⽽根据块数≤√n的基本性质,我们还可以预处理出c i,x表⽰前i个块中值⼩于等于x的数的个数。
接下来,我们分别讨论散块与不同主体之间产⽣的贡献。
(设左右端点所在块编号分别为pl,pr)
散块块内:以左散块为例,直接暴⼒枚举散块内每个元素p i,判断是否在值域内,然后给答案加上块内从它向后的后缀中离散化后值在[p i+1,T pl,R+1−1]范围内的数的个数。
散块与整块:以左散块为例,同样直接暴⼒枚举散块内每个元素a i,判断是否在值域内,然后给答案加上整块中值在[a i+1,R]范围内的数的个数。
,T pr,R+1−1]范围内的数的个数散块与散块:暴⼒枚举左散块中的每个元素a i,判断是否在值域内,然后给答案加上右散块中从r向前的前缀中离散化后值在[T pr,a
i
⽽整块与整块之间的贡献⽐较复杂了,对此我们需要再搞⼀个值域分块来求解。
值域分块:求解整块
考虑对值域分块,然后预处理出f i,j,k表⽰第i∼j个块中,值在前k个块内的所有数对于答案的贡献总和。
设值域上下界所在值域块编号分别为dl,dr,则我们通过差分,⽤f pl+1,pr−1,dr−1减去f pl+1,pr−1,dl再减去pl+1∼pr−1这些序列块中由前dl个值域块中的数和第dl+1∼dr−1个值域块中的数组成的顺序对个数即可得出序列整块询问的值域整块的答案。
⽽对于要减去的这玩意⼉,我们直接枚举每个序列块,则其中在前dl个值域块中的数的个数乘上其后在第dl+1∼dr−1个值域块中的数的个数即为它与之后块产⽣的错误贡献。
⽽每个序列块内部的错误贡献,可以事先预处理⼀个g i,j,k表⽰第i个块中,离散化后值⼩于等于j的数与离散化后值在j+1∼k范围内的数组成的顺序对个数,那么把第dl个值域块的右端点和第dr−1个值域块的右端点离散化后询问即可。
最后还要求解序列整块询问的值域散块的答案,这其实和普通的分块差不多,散块直接暴⼒枚举,判断在序列上是否位于询问的序列整块中,然后考虑它的贡献即可。
⼀个奇怪的散块优化
对于⼀个散块,如果我们的询问区间⼩于块⼤⼩的⼀半就直接询问,否则我们⽤总贡献减去剩余区间的贡献。
这样可以保证每次处理的散块⼤⼩都不超过块⼤⼩的⼀半,实测有⽐较显著的优化。
代码:O(n√n)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define BS 320
#define LL long long
using namespace std;
int n,a[N+5],id[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
#define C(s,x1,x2) (s[x2]-s[x1-1])//⼀维数组差分
#define G(s,x1,x2,y1,y2) (C(s[x2],y1,y2)-C(s[x1-1],y1,y2))//⼆维数组两维分别差分
int pre[N+5][BS+5],suf[N+5][BS+5],c0[BS+5][N+5],c[BS+5][N+5],T[BS+5][N+5],g[BS+5][BS+5][BS+5];LL f[BS+5][BS+5][BS+5];
int sz,bl[N+5],LS[BS+5],RS[BS+5],p[N+5],s[N+5],o[BS+5];I void Build()//预处理
{
RI i,j,k;for(sz=sqrt(n),i=1;i<=n;++i) !LS[bl[i]=(i-1)/sz+1]&&(LS[bl[i]]=i),RS[bl[i]]=i,s[i]=a[i];LS[bl[n]+1]=n+1;//记录每个位置所在块及每个块的左右端点
for(i=1;i<=bl[n];++i)//枚举每个块预处理信息
{
for(sort(s+LS[i],s+RS[i]+1),j=LS[i];j<=RS[i];++j) T[i][s[j]]=j-LS[i]+1;T[i][n+1]=j-LS[i]+1;//离散化
for(j=n;j;--j) !T[i][j]&&(T[i][j]=T[i][j+1]);for(j=LS[i];j<=RS[i];++j) o[p[j]=T[i][a[j]]]=j;//求出每个数后继的离散化结果;记录每个位置离散化值
for(j=LS[i];j<=RS[i];++j) {if(j^LS[i]) for(k=1;k<=T[i][n+1];++k) pre[j][k]=pre[j-1][k];for(k=p[j];k<=T[i][n+1];++k) ++pre[j][k];}//所在前缀离散化后每个数的个数
for(j=RS[i];j>=LS[i];--j) {if(j^RS[i]) for(k=1;k<=T[i][n+1];++k) suf[j][k]=suf[j+1][k];for(k=p[j];k<=T[i][n+1];++k) ++suf[j][k];}//所在后缀离散化后每个数的个数
for(j=LS[i];j<=RS[i];++j) ++c0[i][a[j]];for(j=1;j<=n;++j) c[i][j]=c[i-1][j]+(c0[i][j]+=c0[i][j-1]);//统计前i个块每个数的个数
for(j=1;j<=T[i][n+1];++j) for(k=j+1;k<=T[i][n+1];++k) g[i][j][k]=g[i][j][k-1]+(k^T[i][n+1]?pre[o[k]][j]:0);//预处理块内离散化后1~j的数与j+1~k的数组成的顺序对数 }
for(i=1;i<=bl[n];++i) for(j=i;j<=bl[n];++j)//枚举i~j的块
{
for(k=LS[j];k<=RS[j];++k) f[i][j][bl[a[k]]]+=pre[k][p[k]-1]+c[j-1][a[k]]-c[i-1][a[k]];//计算加⼊数的贡献
for(k=1;k<=bl[n];++k) f[i][j][k]+=f[i][j][k-1];for(k=1;k<=bl[n];++k) f[i][j][k]+=f[i][j-1][k];//⼆维前缀和
}
}
I LL Q(CI l,CI r,CI L,CI R)//询问[l,r]中值在[L,R]范围内的顺序对数
{
RI i,j,pl=bl[l],pr=bl[r],vl,vr,t1=0,t2=0;LL t=0;
if(pl==pr) {for(vl=T[pl][L],vr=T[pl][R+1]-1,i=l;i<=r;++i) L<=a[i]&&a[i]<=R&&(t+=G(pre,i+1,r,p[i],vr));return t;}//同⼀序列块
for(vl=T[pl][L],vr=T[pl][R+1]-1,i=l;i<=RS[pl];++i) L<=a[i]&&a[i]<=R&&(t+=C(suf[i],p[i]+1,vr)+G(c,pl+1,pr-1,a[i],R));//左散块内部;左散块与整块
for(vl=T[pr][L],vr=T[pr][R+1]-1,i=LS[pr];i<=r;++i) L<=a[i]&&a[i]<=R&&(t+=C(pre[i],vl,p[i]-1)+G(c,pl+1,pr-1,L,a[i]));//右散块内部;右散块与整块
for(i=l;i<=RS[pl];++i) L<=a[i]&&a[i]<=R&&(t+=C(pre[r],T[pr][a[i]],vr));if(pl+1>pr-1) return t;//散块与散块;没有整块直接退出
RI k,dl=bl[L],dr=bl[R],tl=RS[dl]+1,tr=LS[dr]-1,gl=RS[pl],gr=LS[pr];
if(dl==dr) {for(i=L;i<=R;++i) gl<(k=id[i])&&k<gr&&(t+=C(pre[k],T[bl[k]][L],p[k]-1)+G(c,pl+1,bl[k]-1,L,i));return t;}//同⼀值域块
if(RS[dl]-L+1<sz/2) for(i=L;i<=RS[dl];++i) gl<(k=id[i])&&k<gr&&(t+=C(suf[k],p[k]+1,T[bl[k]][R+1]-1)+G(c,bl[k]+1,pr-1,i,R));//左侧值域块
else {for(i=LS[dl];i<L;++i) gl<(k=id[i])&&k<gr&&(t-=C(suf[k],p[k]+1,T[bl[k]][R+1]-1)+G(c,bl[k]+1,pr-1,i,R));tl=RS[--dl]+1;}//询问块过⼤则⽤总答案减剩余部分贡献 if(R-LS[dr]+1<sz/2) for(i=LS[dr];i<=R;++i) gl<(k=id[i])&&k<gr&&(t+=C(pre[k],T[bl[k]][tl],p[k]-1)+G(c,pl+1,bl[k]-1,tl,a[k]));//右侧值域块
else {for(i=R+1;i<=RS[dr];++i) gl<(k=id[i])&&k<gr&&(t-=C(pre[k],T[bl[k]][tl],p[k]-1)+G(c,pl+1,bl[k]-1,tl,a[k]));tr=LS[++dr]-1;}//询问块过⼤则⽤总答案减剩余部分贡献 for(t+=C(f[pl+1][pr-1],dl+1,dr-1),i=pl+1;i<pr;++i) t-=g[i][T[i][tl]-1][T[i][tr+1]-1]+1LL*c0[i][tl-1]*G(c,i+1,pr-1,tl,tr);return t;//先差分,然后减去错误贡献
}
int main()
{
RI Qt,i;for(read(n,Qt),i=1;i<=n;++i) read(a[i]),id[a[i]]=i;Build();//记录每个数所在位置,然后预处理
RI x1,x2,y1,y2;W(Qt--) read(x1,x2,y1,y2),writeln(Q(x1,x2,y1,y2));return clear(),0;//询问
}
Processing math: 100%。