peiwenjun's blog 没有知识的荒原

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:如果你没有富裕的空闲时间 & 自信的代码能力 & 足够的耐心,不建议碰这道题。

前置知识: P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I

先对下标分块,那么一个询问区间 [l,r] 被拆成了散块 + 整块 + 散块,如下图:

image

如果询问的左右端点属于相同块,会在讲散块内部贡献时提到。

否则贡献可以分为如下几类:

  • 散块内部。(红\(\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\) 大的数的个数,可以通过 poss 数组 \(\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;
}

posted on 2022-06-18 00:30  peiwenjun  阅读(18)  评论(0)    收藏  举报

导航