Loading

题解 【P3149 排序】

\(\Large\texttt{P3149}\)

标签:树状数组(求逆序对)

有点套路

题意

在一段序列中,每次选定一个数,将这个序列中小于这个数的所有数排序,并顺序插入它们原来的位置上。(本次操作会对下一次影响

问每次操作完了之后(和没操作时)的逆序对个数。

这道题目的逆序对意思有点奇怪,刚开始的逆序对包括相等的两个数

思路

以下操作 \(n\) 指对第 \(n\) 个数进行上述操作

先离散化。

对于一个较大数的操作,是可以从较小的数的操作推导过来的,我们可以这样想:

假设现在已经对于数操作 \(n\) 完了,那么对于操作 \(n+1\) ,它相对于上一个操作减少的逆序对只是在每个数 \(n+1\) 后面小于这个数的个数和。

可以这样理解:

  1. 第一种逆序对,是两个小于 \(n+1\) 的数组成,显然已经没有了,因为操作n已经去除了。

  2. 第二种逆序对,是一个数 \(n+1\) 和小于 \(n+1\) 的数组成,这种会减少(排序一下,小的数到它前面了),也是要计算的。

  3. 第三种逆序对,是一个数 \(n+1\) 和大于 \(n+1\) 的数组成,这可能会改变,但是操作过后每个数 \(n+1\) 的位置上会变为小于等于自己的数,和大于 \(n+1\) 组成的逆序对个数一样,因此,并未改变。

  4. 第四种逆序对,是两个大于 \(n+1\) 的数组成,显然个数不变。

理清了思路,发现第二种逆序对正好可以用树状数组动态解决,操作 \(n\) 过后,维护小于等于 \(n\) 个数的前缀。

并且注意,它的操作是会延续到下一次的,因此注意下下一次操作小于本次操作。

时间复杂度 \(\texttt{O(N logN)}\)

注意会暴 \(\texttt{int}\)

代码

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

#define PB push_back
#define int long long
const int N = 1e6;
inline int read()
{
    int s = 0;
    register bool neg = 0;
    register char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        neg |= (c == '-');
    for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
        ;
    s = (neg ? -s : s);
    return s;
}

int a, b, s[N + 10], p[N + 10], top, Ans[N + 10], q[N + 10], t[N + 10];
vector<int> id[N + 10];

inline void add(int n, int m)
{
    for (; n <= a; n += n & -n)
        q[n] += m;
}

inline int query(int n)
{
    int ans = 0;
    for (; n; n -= n & -n)
        ans += q[n];
    return ans;
}

signed main()
{
    a = read();
    b = read();
    for (int i = 1; i <= a; i++)
        s[i] = p[i] = read();
    sort(p + 1, p + a + 1);
    top = unique(p + 1, p + a + 1) - p - 1;
    for (int i = 1; i <= a; i++)
        s[i] = lower_bound(p + 1, p + top + 1, s[i]) - p;
    int ans = 0, qq = 0;
    for (int i = 1; i <= a; i++)
    {
        add(s[i], 1);//求一开始的逆序对。
        qq += t[s[i]];
        ans += i - query(s[i]);
        t[s[i]]++;
    }
    memset(q, 0, sizeof(q));
    ans += qq;
    printf("%lld\n", ans);
    for (int i = 1; i <= a; i++)
        id[s[i]].PB(i);
    int sum = 0;
    for (int i = 1; i <= top; i++)
    {
        for (int j = 0; j < id[i].size(); j++)
        {
            ans -= sum - query(id[i][j]);
        }
        ans -= id[i].size() - 1;//应对题目奇奇怪怪的逆序对定义。
        for (int j = 0; j < id[i].size(); j++)
        {   
            add(id[i][j], 1);//维护小于等于n个数的前缀
            Ans[id[i][j]] = ans;//储存答案
        }
        sum += id[i].size();
    }
    int x;
    int mx = 1e18;
    for (int i = 1; i <= b; i++)
    {
        x = read();
        mx = min(mx, Ans[x]);
        printf("%lld\n", mx);
    }
    return 0;
}
posted @ 2020-10-07 19:04  RedreamMer  阅读(118)  评论(0编辑  收藏  举报