洛谷题面传送门

zszz,lxl 出的 DS 都是卡常题(

首先由于此题强制在线,因此考虑分块,我们那么待查询区间 \([l,r]\) 可以很自然地被分为三个部分:

  • 左散块
  • 中间的整块
  • 右散块

那么这样一来区间逆序对的来源可以有以下几种:

  • 左散块内部的区间逆序对
  • 右散块内部的区间逆序对
  • 每个整块内部的区间逆序对
  • 左散块与中间整块之间的逆序对
  • 右散块与中间整块之间的逆序对
  • 中间整块两两之间的逆序对
  • 左散块与右散块之间的逆序对

对于前三种情况,我们可以记录这样两个数组:

  • \(pre_j\):\(j\) 所在整块左端点到 \(j\) 这段区间内逆序对个数
  • \(suf_j\):\(j\) 到 \(j\) 所在整块右端点这段区间内逆序对个数

对于第四、五两种情况,显然是要求一个区间内比某个数小/大的数的个数,这个可以通过维护以下数组求出:

  • \(lft_{i,j}\):在前 \(j\) 个整块中有多少个数 \(>a_i\)
  • \(rit_{i,j}\):在第 \(j\) 个整块到第 \(cnt\) 个整块中有多少个数 \(<a_i\)

求出这两东东之后第四、五种情况一边前缀和带走即可。

对于第六种情况我们也类似地维护以下数组:

  • \(bl_{i,j}\):第 \(i\) 个整块与前 \(j\) 个整块之间有多少个逆序对

对于第七种情况貌似没有什么方法直接处理,不过考虑一个 trick 叫归并排序,对于两个排好序的有序数组,我们是可以在线性时间内求出它们排好序后的逆序对个数的。因此我们维护每个块排好序的数组,那么我们在 \(l\) 所在的块与 \(r\) 所在的块中跑一遍双针,即在第 \(l\) 个块排好序的数组中枚举一个 \(a_i\),那么显然在第 \(r\) 个块排好序的数组中,满足 \(a_j<a_i\) 的 \(j\) 显然组成一段前缀,那么我们就双针找出这段前缀,然后统计一下这段前缀中有多少个位置 \(\le r\) 即可。

上述情况都是针对 \(l,r\) 不在同一个整块中的情况,对于 \(l,r\) 在同一个整块中的情况,我们设 \(l,r\) 所在的整块为 \([L,R]\),那么区间 \([l,r]\) 的逆序对个数就可以用 \([L,r]\) 的逆序对个数减去 \([L,l-1]\) 中逆序对的个数,再减去 \([L,l-1]\) 与 \([l,r]\) 之间的逆序对个数,前两个值都可以通过我们之前维护的 \(pre\) 数组求出,至于第三个……相信聪明的读者们如果明白了上面的第七种情况,也能很快知道这个怎么维护。

时间复杂度 \((n+m)\sqrt{n}\),然鹅由于实现的不好,它 TLE 了,只过了第 5 个测试点。

附:20pts 的代码:

const int MAXN=1e5;
const int BLK=500;
int n,qu,a[MAXN+5],pre[MAXN+5],suf[MAXN+5],buc[MAXN+5];
pii b[BLK+5][BLK+5];int sum[BLK+5][MAXN+5];
int lft[MAXN+5][BLK+5],rit[MAXN+5][BLK+5];
int bl[BLK+5][BLK+5];
int blk_cnt=0,blk_sz=0,L[BLK+5],R[BLK+5],bel[MAXN+5];
int t[MAXN+5];
void add(int x,int v){for(int i=x;i<=n;i+=(i&(-i))) t[i]+=v;}
int ask(int x){int ret=0;for(int i=x;i;i&=(i-1)) ret+=t[i];return ret;}
ll query(int l,int r){
if(l<1||l>n||r<1||r>n||l>r) return 0;
if(bel[l]==bel[r]){
ll sum=pre[r];
if(l^L[bel[l]]) sum-=pre[l-1];
for(int i=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
if(b[bel[l]][i].se<l) sum-=c;
c+=(l<=b[bel[l]][i].se&&b[bel[l]][i].se<=r);
} return sum;
} else {
ll sum=0;sum+=suf[l];sum+=pre[r];
for(int i=bel[l]+1;i<bel[r];i++) sum+=pre[R[i]];
for(int i=bel[l]+1;i<bel[r];i++) sum+=bl[i][i-1]-bl[i][bel[l]];
for(int i=l;i<=R[bel[l]];i++) sum+=rit[i][bel[l]+1]-rit[i][bel[r]];
for(int i=L[bel[r]];i<=r;i++) sum+=lft[i][bel[r]-1]-lft[i][bel[l]];
for(int i=1,j=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
while(j<=R[bel[r]]-L[bel[r]]+1&&b[bel[r]][j].fi<b[bel[l]][i].fi){
c+=(b[bel[r]][j].se<=r);j++;
} if(b[bel[l]][i].se>=l) sum+=c;
}
return sum;
}
}
int main(){
scanf("%d%d",&n,&qu);
blk_sz=400;blk_cnt=(n-1)/blk_sz+1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,n);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
}
for(int i=1;i<=n;i++) b[bel[i]][i-L[bel[i]]+1]=mp(a[i],i);
for(int i=1;i<=blk_cnt;i++) sort(b[i]+1,b[i]+R[i]-L[i]+2);
for(int i=1;i<=n;i++) sum[bel[i]][a[i]]++;
for(int i=1;i<=blk_cnt;i++) for(int j=1;j<=n;j++)
sum[i][j]+=sum[i][j-1];
for(int i=1;i<=blk_cnt;i++){
memset(t,0,sizeof(t));ll sum=0;
for(int j=L[i];j<=R[i];j++){
pre[j]=(sum+=j-L[i]-ask(a[j]));
add(a[j],1);
} memset(t,0,sizeof(t));sum=0;
for(int j=R[i];j>=L[i];j--){
suf[j]=(sum+=ask(a[j]));
add(a[j],1);
}
}
for(int i=1;i<=blk_cnt;i++){
for(int j=1;j<=n;j++) buc[j]+=sum[i][j];
for(int j=R[i]+1;j<=n;j++) lft[j][i]=R[i]-buc[a[j]];
} memset(buc,0,sizeof(buc));
for(int i=blk_cnt;i;i--){
for(int j=1;j<=n;j++) buc[j]+=sum[i][j];
for(int j=L[i]-1;j;j--) rit[j][i]=buc[a[j]-1];
}
for(int i=1;i<=blk_cnt;i++) for(int j=1;j<i;j++) for(int k=L[i];k<=R[i];k++) bl[i][j]+=lft[k][j];
ll pre=0;
while(qu--){
ll l,r;scanf("%lld%lld",&l,&r);l^=pre;r^=pre;
printf("%lld\n",pre=query(l,r));
}
return 0;
}

然后就到了奇淫的卡常时间了,我首先玄学般地三分了下块长并加了个 IO 优化,然鹅并没有什么卵用,该 T 的还是 T 掉了。

其次我们注意到再求 \(lft,rit\) 的过程用到了维护第 \(i\) 个块 occurrence 的前缀和数组 \(sum_{i,j}\),事实上这东西大可不必提前预处理出来,这样耗时间又耗空间,因此我将它改为,维护一个临时数组 \(c_j\),扫到第 \(i\) 块时再求出这一块的前缀和,我还加了一个小小的剪枝,就是求出这一块中 \(a_i\) 的最小值 \(mn\),求前缀和时从 \(mn\) 开始枚举,正确性显然,但是似乎用处不大。

接下来我发现这个 \(lft\) 和 \(rit\) 数组定义与功能类似,都可以求某个前缀小于某个数的个数,事实上它们完全可以合并成一个数组,因此我就把求 \(rit\) 那一部分的 \(n\sqrt{n}\) 删掉,然后几个细节稍微改了改,常数确实小了不少,第五个测试点又原来 546ms 变成了 371ms,第四个点时而 A 时而 T,剩下三个点仍然 TLE。

附:40pts 的代码:

using namespace fastio;
const int MAXN=1e5;
const int BLK=500;
int n,qu,a[MAXN+5],pre[MAXN+5],suf[MAXN+5],buc[MAXN+5];
pii b[BLK+5][BLK+5];int c[MAXN+5];
int lt[MAXN+5][BLK+5],bl[BLK+5][BLK+5];
int blk_cnt=0,blk_sz=0,L[BLK+5],R[BLK+5],bel[MAXN+5];
int t[MAXN+5];
void add(int x,int v){for(int i=x;i<=n;i+=(i&(-i))) t[i]+=v;}
int ask(int x){int ret=0;for(int i=x;i;i&=(i-1)) ret+=t[i];return ret;}
ll query(int l,int r){
if(l<1||l>n||r<1||r>n||l>r) return 0;
if(bel[l]==bel[r]){
ll sum=pre[r];
if(l^L[bel[l]]) sum-=pre[l-1];
for(int i=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
if(b[bel[l]][i].se<l) sum-=c;
c+=(l<=b[bel[l]][i].se&&b[bel[l]][i].se<=r);
} return sum;
} else {
ll sum=0;sum+=suf[l];sum+=pre[r];
for(int i=bel[l]+1;i<bel[r];i++) sum+=pre[R[i]]+bl[i][i-1]-bl[i][bel[l]];
for(int i=l;i<=R[bel[l]];i++) sum+=lt[i][bel[r]-1]-lt[i][bel[l]];
for(int i=L[bel[r]];i<=r;i++) sum+=(L[bel[r]]-R[bel[l]]-1)-(lt[i][bel[r]-1]-lt[i][bel[l]]);
for(int i=1,j=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
while(j<=R[bel[r]]-L[bel[r]]+1&&b[bel[r]][j].fi<b[bel[l]][i].fi){
c+=(b[bel[r]][j].se<=r);j++;
} if(b[bel[l]][i].se>=l) sum+=c;
}
return sum;
}
}
int main(){
read(n);read(qu);
blk_sz=450;blk_cnt=(n-1)/blk_sz+1;
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,n);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
}
for(int i=1;i<=n;i++) b[bel[i]][i-L[bel[i]]+1]=mp(a[i],i);
for(int i=1;i<=blk_cnt;i++) sort(b[i]+1,b[i]+R[i]-L[i]+2);
for(int i=1;i<=blk_cnt;i++){
memset(t,0,sizeof(t));ll sum=0;
for(int j=L[i];j<=R[i];j++){
pre[j]=(sum+=j-L[i]-ask(a[j]));
add(a[j],1);
} memset(t,0,sizeof(t));sum=0;
for(int j=R[i];j>=L[i];j--){
suf[j]=(sum+=ask(a[j]));
add(a[j],1);
}
}
for(int i=1;i<=blk_cnt;i++){
int mn=n+1,s=0;
for(int j=L[i];j<=R[i];j++) c[a[j]]++,chkmin(mn,a[j]);
for(int j=mn;j<=n;j++) s+=c[j],buc[j]+=s;
for(int j=L[i];j<=R[i];j++) c[a[j]]--;
for(int j=1;j<=n;j++) lt[j][i]=buc[a[j]-1];
}
for(int i=1;i<=blk_cnt;i++) for(int j=1;j<i;j++)
for(int k=L[i];k<=R[i];k++) bl[i][j]+=R[j]-lt[k][j];
ll pre=0;
while(qu--){
ll l,r;read(l);read(r);l^=pre;r^=pre;
print(pre=query(l,r),'\n');
} print_final();
return 0;
}

然后就到了最奇怪的部分了,我注意到询问的时候写了这样一句话:

for(int i=l;i<=R[bel[l]];i++) sum+=lt[i][bel[r]-1]-lt[i][bel[l]];

然后我就灵机一动,尝试交换 \(lt\) 的两维,并寻思这样效率会加快多少。

然鹅出乎意料的是,作了这样一个小小的变动后,它竟然 AC 了!而且竟然还抢到了(目前,截至 2021.7.18)的最优解。

为什么我会想到作这样一个变动?注意到在上面的语句中,后面一维的值与枚举变量 \(i\) 无关,因此如果交换这两维,后面一维访问的下标就是一段连续的区间,访问的地址也是一段完整的区间,这样就能大大提高访问速度。

AC 代码:

using namespace fastio;
const int MAXN=1e5;
const int BLK=500;
int n,qu,a[MAXN+5],pre[MAXN+5],suf[MAXN+5],buc[MAXN+5];
pii b[BLK+5][BLK+5];int c[MAXN+5];
int lt[BLK+5][MAXN+5],bl[BLK+5][BLK+5];
int blk_cnt=0,blk_sz=0,L[BLK+5],R[BLK+5],bel[MAXN+5];
int t[MAXN+5];
void add(int x,int v){for(int i=x;i<=n;i+=(i&(-i))) t[i]+=v;}
int ask(int x){int ret=0;for(int i=x;i;i&=(i-1)) ret+=t[i];return ret;}
ll query(int l,int r){
if(l<1||l>n||r<1||r>n||l>r) return 0;
if(bel[l]==bel[r]){
ll sum=pre[r];
if(l^L[bel[l]]) sum-=pre[l-1];
for(int i=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
if(b[bel[l]][i].se<l) sum-=c;
c+=(l<=b[bel[l]][i].se&&b[bel[l]][i].se<=r);
} return sum;
} else {
ll sum=0;sum+=suf[l];sum+=pre[r];
for(int i=bel[l]+1;i<bel[r];i++) sum+=pre[R[i]]+bl[i][i-1]-bl[i][bel[l]];
for(int i=l;i<=R[bel[l]];i++) sum+=lt[bel[r]-1][i]-lt[bel[l]][i];
for(int i=L[bel[r]];i<=r;i++) sum+=(L[bel[r]]-R[bel[l]]-1)-(lt[bel[r]-1][i]-lt[bel[l]][i]);
for(int i=1,j=1,c=0;i<=R[bel[l]]-L[bel[l]]+1;i++){
while(j<=R[bel[r]]-L[bel[r]]+1&&b[bel[r]][j].fi<b[bel[l]][i].fi){
c+=(b[bel[r]][j].se<=r);j++;
} if(b[bel[l]][i].se>=l) sum+=c;
}
return sum;
}
}
int main(){
read(n);read(qu);
blk_sz=460;blk_cnt=(n-1)/blk_sz+1;
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,n);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
}
for(int i=1;i<=n;i++) b[bel[i]][i-L[bel[i]]+1]=mp(a[i],i);
for(int i=1;i<=blk_cnt;i++) sort(b[i]+1,b[i]+R[i]-L[i]+2);
for(int i=1;i<=blk_cnt;i++){
memset(t,0,sizeof(t));ll sum=0;
for(int j=L[i];j<=R[i];j++){
pre[j]=(sum+=j-L[i]-ask(a[j]));
add(a[j],1);
} memset(t,0,sizeof(t));sum=0;
for(int j=R[i];j>=L[i];j--){
suf[j]=(sum+=ask(a[j]));
add(a[j],1);
}
}
for(int i=1;i<=blk_cnt;i++){
int mn=n+1,s=0;
for(int j=L[i];j<=R[i];j++) c[a[j]]++,chkmin(mn,a[j]);
for(int j=mn;j<=n;j++) s+=c[j],buc[j]+=s;
for(int j=L[i];j<=R[i];j++) c[a[j]]--;
for(int j=1;j<=n;j++) lt[i][j]=buc[a[j]-1];
}
for(int i=1;i<=blk_cnt;i++) for(int j=1;j<i;j++)
for(int k=L[i];k<=R[i];k++) bl[i][j]+=R[j]-lt[j][k];
ll pre=0;
while(qu--){
ll l,r;read(l);read(r);l^=pre;r^=pre;
print(pre=query(l,r),'\n');
} print_final();
return 0;
}

洛谷 P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I(分块+卡常)的更多相关文章

  1. [洛谷P5048][Ynoi2019模拟赛]Yuno loves sqrt technology III

    题目大意:有$n(n\leqslant5\times10^5)$个数,$m(m\leqslant5\times10^5)$个询问,每个询问问区间$[l,r]$中众数的出现次数 题解:分块,设块大小为$ ...

  2. 洛谷P5048 [Ynoi2019模拟赛]Yuno loves sqrt technology III(分块)

    传送门 众所周知lxl是个毒瘤,Ynoi道道都是神仙题 用蒲公英那个分块的方法做结果两天没卡过去→_→ 首先我们分块,预处理块与块之间的答案,然后每次询问的时候拆成整块和两边剩下的元素 整块的答案很简 ...

  3. 洛谷 P5048 - [Ynoi2019 模拟赛] Yuno loves sqrt technology III(分块)

    题面传送门 qwq 感觉跟很多年前做过的一道题思路差不多罢,结果我竟然没想起那道题?!!所以说我 wtcl/wq 首先将 \(a_i\) 离散化. 如果允许离线那显然一遍莫队就能解决,复杂度 \(n\ ...

  4. [Luogu5048] [Ynoi2019模拟赛]Yuno loves sqrt technology III[分块]

    题意 长为 \(n\) 的序列,询问区间众数,强制在线. \(n\leq 5\times 10^5\). 分析 考虑分块,暴力统计出整块到整块之间的众数次数. 然后答案还可能出现在两边的两个独立的块中 ...

  5. Luogu P5048 [Ynoi2019模拟赛]Yuno loves sqrt technology III 分块

    这才是真正的$N\sqrt{N}$吧$qwq$ 记录每个数$vl$出现的位置$s[vl]$,和每个数$a[i]=vl$是第几个$vl$,记为$P[i]$,然后预处理出块$[i,j]$区间的答案$f[i ...

  6. [luogu5048] [Ynoi2019模拟赛] Yuno loves sqrt technology III

    题目链接 洛谷. Solution 思路同[BZOJ2724] [Violet 6]蒲公英,只不过由于lxl过于毒瘤,我们有一些更巧妙的操作. 首先还是预处理\(f[l][r]\)表示\(l\sim ...

  7. [Ynoi2019模拟赛]Yuno loves sqrt technology I

    题目描述 给你一个长为n的排列,m次询问,每次查询一个区间的逆序对数,强制在线. 题解 MD不卡了..TMD一点都卡不动. 强制在线的话也没啥好一点的方法,只能分块预处理了. 对于每个块,我们设lef ...

  8. [Ynoi2019模拟赛]Yuno loves sqrt technology III

    题目大意: 给你一个长为n的序列a,m次询问,每次查询一个区间的众数的出现次数,强制在线. 解题思路: 出题人题解 众所周知lxl是个毒瘤,Ynoi道道都是神仙题 首先得离散化. 分块后,预处理Fi, ...

  9. [Ynoi2019模拟赛]Yuno loves sqrt technology II(二次离线莫队)

    二次离线莫队. 终于懂了 \(lxl\) 大爷发明的二次离线莫队,\(\%\%\%lxl\) 二次离线莫队,顾名思义就是将莫队离线两次.那怎么离线两次呢? 每当我们将 \([l,r]\) 移动右端点到 ...

随机推荐

  1. 【UE4 C++】Actor 与 Component —— 创建、销毁

    Actor的生成与销毁 创建Actor实例 UClass* TSubclassOf<T> SpawnActor() UPROPERTY(EditAnywhere, Category = & ...

  2. [no_code]OCR表格处理——功能规格说明书

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 功能规格说明书 我们在这个课程的目标是 远程协同工作,采用最新技术开发软件 这个作业在哪个具体方面 ...

  3. stm32看门狗详细解答,看了觉得一下子明白了很多

    一.独立看门狗 STM32 的独立看门狗由内部专门的 40Khz 低速时钟驱动,即使主时钟发生故障,它也仍然有效. 看门狗的原理:单片机系统在外界的干扰下会出现程序跑飞的现象导致出现死循环,看门狗电路 ...

  4. 实验5:开源控制器实践——POX

    一.实验目的 1.能够理解 POX 控制器的工作原理: 2.通过验证POX的forwarding.hub和forwarding.l2_learning模块,初步掌握POX控制器的使用方法: 3.能够运 ...

  5. shell IO重定向

    I/O重定向 默认情况下,有3个"文件"处于打开状态,stdin,stdout,stderr:重定向的解释:捕捉一个文件,命令,程序,脚本或者脚本中的代码块的输出,然后将这些输出作 ...

  6. 另类加法 牛客网 程序员面试经典 C++ Python

    另类加法  牛客网 程序员面试经典 C++ Python 题目描述 请编写一个函数,将两个数字相加.不得使用+或其他算数运算符. 给定两个int A和B.请返回A+B的值 测试样例: 1,2 返回:3 ...

  7. POJ 2446 Chessboard(二分图最大匹配)

    题意: M*N的棋盘,规定其中有K个格子不能放任何东西.(即不能被覆盖) 每一张牌的形状都是1*2,问这个棋盘能否被牌完全覆盖(K个格子除外) 思路: M.N很小,把每一个可以覆盖的格子都离散成一个个 ...

  8. Python3 装逼神器---词云(wordcloud)

    词云 (Word Cloud)是对文本中出现频率较高的词语给予视觉化展示的图形, 是一种常见的文本挖掘的方法. 实例:     依赖包: # pip3 install wordcloud  jieba ...

  9. 新手使用python以及pycharm看过来

    前言 随着互联网时代的进步,人类与计算机之前的沟通交流越来越便捷,自此交流的媒介--编程语言吸引力更多的人学习,今天我们就来谈谈当前市面上最火的编程语言 1.文件的概念 什么是文件夹.文件 其实是操作 ...

  10. CLion 2021.2 debug报错 process exited with status -1 (attach failed (Not allowed to attach to process.

    Clion 升级 2021.2 版本后 debug 报错: process exited with status -1 (attach failed (Not allowed to attach to ...