1. 程式人生 > 實用技巧 >題解-P3149 【排序】

題解-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;
}