其他分享
首页 > 其他分享> > 洛谷P5047 YnOI2019模拟赛T2 题解

洛谷P5047 YnOI2019模拟赛T2 题解

作者:互联网

Preface

刚看到题目时还以为是云南省选……
人生中第一道黑题!
想当年看大佬写猪国杀(那时还是黑题)时无比仰慕,却始终认为NOI/NOI+/CTSC是多么遥不可及的高峰……
回想一年前,我也就只有橙黄的水平,对稍微高级一点的算法毫无涉猎。当时对OI也没有什么热情,只会线性DP,连深搜广搜都分不清。然而一年之后,我遇到了引我走进算法大门的老师,学了图论,数论,学了数据结构……更重要的是,我找到了自己的目标,从以前那种浑浑噩噩学OI的状态里挣脱了出来。我长出了自己的腿,可以自己攀登,可以苦中作乐。蓦然回首,我已经站在当时仰望的山峰上了。
人始终是向上走的。微观上,可以像电子云,有着放松随意的状态。而在宏观上,却一定要保持尽可能快的速度,且要有加速度。
说多了。看题。

题面

传送门
给定一个长度为 \(N\) 的序列 \(a\) 以及 \(M\) 次询问,每次询问为一个区间,求区间内逆序对个数。
\(1≤N,M≤10^5\),\(0≤a_i≤10^9\)。

题解

首先,区间的增减操作较容易维护信息,考虑莫队。
用一棵权值线段树或树状数组维护信息,可以实现增减复杂度 \(O(\log{N})\)。整体复杂度 \(O(N\sqrt{M}\log{N})\)。显然无法通过此题。
再考虑增减操作,如 \(R\) 向右移动一位。容易发现,\(res\) 的更新满足区间的性质,即前缀和相减。于是想到二次离线莫队。
\(R\) 向右移动一位,\(res\) += \([L,R]\) 中大于 \(a_{R+1}\) 的数的个数。即 \([1,R]\) 大于 \(a_{R+1}\) 个数 - \([1,L-1]\) 大于 \(a_{R+1}\) 个数。利用二次离线莫队的惯用方式,设 \(f(i)\) 表示 \([1,i]\) 中大于 \(a_{i+1}\) 的个数。则:

  1. \(R\) → \(r\):\(res\) += \(\sum_{i=R}^{r-1} f(i) - greater([1,L-1],[R+1,r])\)。其中后项表示:对于 \([R+1,r]\) 中的所有数,求出 \([1,L-1]\) 中大于它的个数,并将结果相加。
  2. \(r\) ← \(R\):\(res\) += \(-\sum_{i=r}^{R-1} f(i) + greater([1,L-1],[r+1,R])\)。

然而,当考虑到 \(L\) 的移动时,发现情况较模板题有所变化。\(L\) 向左移动一位,\(res\) += \([L,R]\) 区间内小于 \(a_{L-1}\) 的个数,即\([1,R]\) 区间内小于 \(a_{L-1}\) 的个数 - \([1,L-1]\) 内小于 \(a_{L-1}\) 的个数。此时比较的方式为“小于”,而非上面 \(f\) 值定义的“大于”。
我们可以与模板题作一下比较。异或,显然是无序的;逆序对,显然是有序的。在模板题的暴力做法中,同样无需大小顺序;而在此题的暴力做法中,\(R\) 的移动求大于,\(L\) 的移动求小于。对应地,我们也给 \(f\) 值再加一维,变成 \(f(i,0)\) 与 \(f(i,1)\),分别对应小于和大于。则上2式中均变为 \(f(i,1)\)。另外,对应 \(greater\),定义 \(less\),含义可以类比。
3. \(L\) → \(l\):\(res\) += \(\sum_{i=L-1}^{l-2} f(i,0) - less([1,R],[L,l-1])\)。
4. \(l\) ← \(L\):\(res\) += \(-\sum_{i=l-1}^{L-2} f(i,0) - less([1,R],[l,L-1])\)。

当你愉快地写出所有式子后,此题看似是结束了。但是,还有一个问题:
回顾模板题中二次离线后的操作,复杂度为 \(O(NS+N\sqrt{M})\),表示扫描序列时每加入一个数用 \(S\) 的时间维护信息,并 \(O(1)\) 查询,记录二次离线的结果。但是此题中,略加分析会发现复杂度为 \(O(N\log{N}+N\sqrt{M}\log{N})\)。查询还是带有一个 \(log\),还不如不用二次离线!
那么该怎么办呢?发现复杂度中前项还很充裕,可以再消耗一点时间,维护更多信息。既然我们的目标是 \(O(1)\) 得出查询的答案,那么不难想到用能维护更多信息的分块代替线段树/树状数组。对值域建立分块,分成 \(\sqrt{T}\) 块,其中 \(T\) 表示离散化后数字的个数。加入一个数后,将其块内的数的信息暴力处理好,其他块打上懒惰标记。

void Add(int x) {
    for (int i = x - 1; posv[i] == posv[x]; i--) rk[i][1]++; //rank表示块内有多少数比其大/小
    for (int i = x + 1; posv[i] == posv[x]; i++) rk[i][0]++;
    for (int i = 1; i < posv[x]; i++) add[i][1]++;
    for (int i = block; i > posv[x]; i--) add[i][0]++; //block为总块数
}

不难看出,此时rk+add即为总共大于/小于此数的数的个数。可以 \(O(1)\) 查询。总时间复杂度 \(O(N\sqrt{M})\)。

最后说一句,此题时限250ms,此做法需要卡常。可以加上快读快输,并在使用频繁的变量前加上register(寄存器优化)。但不要加太多,否则速度可能更慢。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
int n, m, a[N], pos[N], f[N][2];
int mp[N], mpt;
int block, posv[N], rk[N][2], add[N][2];
ll ans[N];
struct Query {
    int id, l, r;
    ll res;
    bool operator <(const Query &oth) const {
        return pos[l] != pos[oth.l] ? pos[l] < pos[oth.l] : r < oth.r;
    }
} q[N];
struct Range {
    int id, l, r, k, type;
};
vector<Range> range[N];
int read() {
    int x = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
    return x;
}
void write(ll x) {
    if (x > 9) write(x / 10);
    putchar('0' + x % 10);
}
void Add(int x) {
    for (int i = x - 1; posv[i] == posv[x]; i--) rk[i][1]++;
    for (int i = x + 1; posv[i] == posv[x]; i++) rk[i][0]++;
    for (int i = 1; i < posv[x]; i++) add[i][1]++;
    for (int i = block; i > posv[x]; i--) add[i][0]++;
}
int main() {
    n = read(); m = read();
    for (int i = 1; i <= n; i++) mp[i] = a[i] = read();
    sort(mp + 1, mp + n + 1);
    mpt = unique(mp + 1, mp + n + 1) - (mp + 1);
    for (int i = 1; i <= n; i++) a[i] = lower_bound(mp + 1, mp + mpt + 1, a[i]) - mp;
    int lenv = max(1, (int)sqrt(mpt));
    block = (mpt - 1) / lenv + 1;
    for (int i = 1; i <= mpt; i++) posv[i] = (i - 1) / lenv + 1;
    for (int i = 1; i <= n; i++) {
        Add(a[i]);
        f[i][1] = rk[a[i + 1]][1] + add[posv[a[i + 1]]][1];
        f[i][0] = rk[a[i + 1]][0] + add[posv[a[i + 1]]][0];
    }
    int len = max(1, (int)sqrt((double)n * n / m));
    for (int i = 1; i <= n; i++) pos[i] = (i - 1) / len + 1;
    for (int i = 1; i <= m; i++) q[i] = (Query){i, read(), read()};
    sort(q + 1, q + m + 1);
    for (int i = 1, L = 1, R = 0; i <= m; i++) {
        int l = q[i].l, r = q[i].r;
        if (R < r) range[L - 1].push_back((Range){i, R + 1, r, 1, -1});
        while (R < r) q[i].res += f[R][1], R++;
        if (R > r) range[L - 1].push_back((Range){i, r + 1, R, 1, 1});
        while (R > r) q[i].res -= f[R - 1][1], R--;
        if (L < l) range[R].push_back((Range){i, L, l - 1, 0, -1});
        while (L < l) q[i].res += f[L - 1][0], L++;
        if (L > l) range[R].push_back((Range){i, l, L - 1, 0, 1});
        while (L > l) q[i].res -= f[L - 2][0], L--;
    }
    memset(rk, 0, sizeof (rk));
    memset(add, 0, sizeof (add));
    for (int i = 1; i <= n; i++) {
        Add(a[i]);
        for (register int j = 0; j < range[i].size(); j++) {
            Range x = range[i][j];
            for (register int k = x.l; k <= x.r; k++)
                q[x.id].res += (rk[a[k]][x.k] + add[posv[a[k]]][x.k]) * x.type;
        }
    }
    for (int i = 1; i <= m; i++) {
        q[i].res += q[i - 1].res; ans[q[i].id] = q[i].res;
    }
    for (int i = 1; i <= m; i++) {
        write(ans[i]); putchar('\n');
    }
    return 0;
}

标签:YnOI2019,洛谷,int,题解,个数,++,res,posv,rk
来源: https://www.cnblogs.com/Fisher-Y/p/16107695.html