NC14522 珂朵莉的数列

题目链接

题目

题目描述

珂朵莉给了你一个序列,有 \(\frac{n\times(n+1)}2\) 个子区间,求出她们各自的逆序对个数,然后加起来输出

输入描述

第一行一个数 n 表示这个序列 a 的长度之后一行 n 个数,第i个数表示ai

输出描述

输出一行一个数表示答案

示例1

输入

10
1 10 8 5 6 2 3 9 4 7

输出

270

示例2

输入

20
6 0 4 5 8 8 0 6 6 1 0 4 6 6 0 0 7 2 0 5

输出

3481

备注

对于100%的数据,n <=1000000 ,0 <= 序列中每个数 <= 1000000000

题解

知识点:线段树,离散化,枚举。

这道题在经典的逆序对问题上加了一点点东西。

对于经典逆序对问题,是通过从左到右枚举每个数,对于某个数求出在它左边且大于它的数的个数,利用线段树或树状数组的权值和解决的。在这道题,我们可以借用这个思路。

假设数组 \(a\)\(n\) 个数,考虑一对逆序对 \((x,y),x<y\) 产生的贡献,显然是包含其的区间个数 \(x(n-y+1)\) ,其中 \(a_x\) 贡献了 \(x\) 个有效点, \(a_y\) 贡献了 \(n - y + 1\) 个有效点。

我们固定右端点 \(y\) ,那么产生的贡献为 \((n-y+1)\displaystyle \sum_{x<y,a_x > a_y}x\) ,只需要求出在它左边且大于它的数贡献的有效点的个数和,只需要将逆序对问题中的权值从个数替换为贡献的有效点个数即可。

因此,我们从左到右枚举每个数(枚举顺序维护了在左边的偏序关系),用权值线段树维护出现过的数的权值(有效点个数),随后只需要询问大于当前数的权值和(线段树维护了大于的偏序关系),即为 \(\displaystyle \sum_{x<y , a_x>a_y}x\)

另外,本题数据范围要离散化,结果超过 long long ,要用 __int128_t

时间复杂度 \(O(n \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

template<class T>
struct Discretization {
    vector<T> uniq;
    Discretization() {}
    Discretization(const vector<T> &src) { init(src); }
    void init(const vector<T> &src) {
        uniq = src;
        sort(uniq.begin() + 1, uniq.end());
        uniq.erase(unique(uniq.begin() + 1, uniq.end()), uniq.end());
    }
    int get(T x) { return lower_bound(uniq.begin() + 1, uniq.end(), x) - uniq.begin(); }
};

struct T {
    ll sum;
    static T e() { return { 0 }; }
    friend T operator+(const T &a, const T &b) { return { a.sum + b.sum }; }
};
struct F {
    int add;
    T operator()(const T &x) { return { x.sum + add }; }
};

template<class T, class F>
class SegmentTree {
    int n;
    vector<T> node;

    void update(int rt, int l, int r, int x, F f) {
        if (r < x || x < l) return;
        if (l == r) return node[rt] = f(node[rt]), void();
        int mid = l + r >> 1;
        update(rt << 1, l, mid, x, f);
        update(rt << 1 | 1, mid + 1, r, x, f);
        node[rt] = node[rt << 1] + node[rt << 1 | 1];
    }

    T query(int rt, int l, int r, int x, int y) {
        if (r < x || y < l) return T::e();
        if (x <= l && r <= y) return node[rt];
        int mid = l + r >> 1;
        return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
    }

public:
    SegmentTree(int _n = 0) { init(_n); }

    void init(int _n) {
        n = _n;
        node.assign(n << 2, T::e());
    }

    void update(int x, F f) { update(1, 1, n, x, f); }

    T query(int x, int y) { return query(1, 1, n, x, y); }
};

template<class T>
inline void write(T x) {
    if (x < 0) { putchar('-');x = -x; }
    if (x >= 10) write(x / 10);
    putchar(x % 10 + '0');
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1;i <= n;i++) cin >> a[i];

    Discretization<int> dc(a);
    int n_rk = dc.uniq.size() - 1;
    SegmentTree<T, F> sgt(n_rk);

    // 假设i < j,若有逆序对(i,j),那么贡献为i*(n-j+1)
    // 从左往右枚举逆序对右侧的数字a[i],则可以累加左侧数字,即[1,i-1],>a[i]的数的贡献,最后乘n-i+1即可
    __int128_t ans = 0;
    for (int i = 1;i <= n;i++) {
        ans += sgt.query(dc.get(a[i]) + 1, n_rk).sum * (n - i + 1);
        sgt.update(dc.get(a[i]), { i });
    }

    write(ans);
    puts("");
    return 0;
}
posted @ 2023-05-01 17:02  空白菌  阅读(19)  评论(0编辑  收藏  举报