AcWing_1215 小朋友排队(归并排序/树状数组/线段树)

AcWing

1215. 小朋友排队

总共要交换的次数是数组中逆序对的数量,对于某一个小朋友来说,他要被移动的次数,就是以他为逆序对左端点或右端点的次数之和。

求逆序对,可以使用归并排序的方式。对于每次归并之前,求跨越左右区间的所有逆序对数量,并更新小朋友被移动的次数。当所有的归并结束后,每个小朋友被移动的次数就确定了。

归并排序,要注意使用pair<int, int>存储身高和序号,避免排序时打乱顺序:

#include <bits/stdc++.h>
using namespace std;
int n;
const int N = 100010;
typedef pair<int, int> PII;
#define hh first
#define id second
PII h[N], tmp[N];
int cnt[N];
typedef long long LL;
void mergesort(int l, int r){
    if(l >= r) return;
    int mid = l + r >> 1;
    mergesort(l, mid);
    mergesort(mid + 1, r);
    int pl = l, pr = mid + 1, pt;
    // 对于右边区间的每一个点,找到左边区间大于它的点的数量
    while(pr <= r){
        while(pl <= mid && h[pl].hh <= h[pr].hh) pl++;
        cnt[h[pr].id] += (mid - pl + 1);
        pr++;
    }
    pl = l, pr = mid + 1;
    // 对于左边区间的每一个点,找到右边区间小于它的点的数量
    while(pl <= mid){
        while(pr <= r && h[pl].hh > h[pr].hh) pr++;
        cnt[h[pl].id] += (pr - (mid + 1));
        pl++;
    }
    pl = l, pr = mid + 1, pt = 1;
    while(pl <= mid && pr <= r){
        if(h[pl].hh <= h[pr].hh) tmp[pt++] = h[pl++];
        else tmp[pt++] = h[pr++];
    }
    while(pl <= mid) tmp[pt++] = h[pl++];
    while(pr <= r) tmp[pt++] = h[pr++];
    for(int i = l, j = 1; i <= r; i++, j++) h[i] = tmp[j];
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++){
        h[i].id = i;
        cin >> h[i].hh;
    }
    mergesort(1, n);
    LL sum = 0;
    for(int i = 1; i <= n; i++) sum += (LL)cnt[i] * (cnt[i] + 1) / 2;
    cout << sum;
    return 0;
}

树状数组用于逆序对,分别正序和逆序动态建立树状数组,存的是截至当前的一系列数值(身高)出现的次数,在建立的过程中,更新某一边的逆序对的数量(即某个小朋友被向左或向右移动的次数),这个过程实质上是区间查询。注意边界,将a[i]全都加1

#include <bits/stdc++.h>
using namespace std;
int n;
const int N = 100010, M = 1000010;
int a[N], tr[M], cnt[N];
typedef long long LL;
int lowbit(int x){return x & -x;}
void update(int x, int v){
    for(int i = x; i <= M - 1; i += lowbit(i)) tr[i] += v;
}
int query(int x){
    int res = 0;
    for(int i = x; i >= 1; i -= lowbit(i)) res += tr[i];
    return res;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
        a[i] ++;
    }
    for(int i = 1; i <= n; i ++){
        cnt[i] += query(M - 1) - query(a[i]);
        update(a[i], 1);
    }
    memset(tr, 0, sizeof tr);
    for(int i = n; i >= 1; i --){
        cnt[i] += query(a[i] - 1);
        update(a[i], 1);
    }
    LL res = 0;
    for(int i = 1; i <= n; i ++) res += (LL)cnt[i] * (cnt[i] + 1) / 2;
    cout << res;
    return 0;
}

线段树也是类似的,但是要注意边界情况:

#include <bits/stdc++.h>
using namespace std;
int n;
const int N = 100010, M = 1000010;
int a[N], sum[4 * M], cnt[N];
typedef long long LL;
void push(int u){
    sum[u] = sum[2*u] + sum[2*u + 1];
}
void update(int l, int r, int u, int x, int v){
    if(l == r){
        sum[u] += v;
        return;
    }
    int mid = l + r >> 1;
    if(x <= mid) update(l, mid, 2*u, x, v);
    else update(mid + 1, r, 2*u + 1, x, v);
    push(u);
}
int query(int L, int R, int l, int r, int u){
    if(L <= l && r <= R) return sum[u];
    int res = 0;
    int mid = l + r >> 1;
    if(L <= mid) res += query(L, R, l, mid, 2*u);
    if(R > mid) res += query(L, R, mid + 1, r, 2*u + 1);
    return res;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
        a[i] ++;
    }
    for(int i = 1; i <= n; i ++){
        cnt[i] += query(a[i] + 1, M - 1, 1, M - 1, 1);
        update(1, M - 1, 1, a[i], 1);
    }
    memset(sum, 0, sizeof sum);
    for(int i = n; i >= 1; i--){
        if(a[i] != 1) cnt[i] += query(1, a[i] - 1, 1, M - 1, 1);
        update(1, M - 1, 1, a[i], 1);
    }
    LL res = 0;
    for(int i = 1; i <= n; i ++) res += (LL)cnt[i] * (cnt[i] + 1) / 2;
    cout << res;
    return 0;
}