蓝桥杯历届试题-小朋友排队(逆序数)

 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
小朋友排队
时间限制:1.0s   内存限制:256.0MB
    
问题描述
  n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。
  每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。
  如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。
  请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。
  如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。
 
输入格式
  输入的第一行包含一个整数$n$,表示小朋友的个数。
  第二行包含$n$个整数 $H_1$ $H_2$ … $H_n$,分别表示每个小朋友的身高。
 
输出格式
  输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
 
样例输入
3
3 2 1
样例输出
9
 
样例说明
  首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。
 
数据规模和约定
  对于10%的数据, $1 \le n \le 10$;
  对于30%的数据, $1 \le n \le 1000$;
  对于50%的数据, $1 \le n \le 10000$;
  对于100%的数据,$1 \le n \le 100000$,$0 \le Hi \le 1000000$。
  ---------------------------------------------------------------------------------------------------------------------------------------------------
一、思路
  我猜想的:对于每个数,统计位置在它左边且数值比它大的数字的个数,同时统计位置在它右边且数值比它小的数字的个数。 统计完成之后,每一个位置对应的计数就是该位置的数字必须交换的最少次数。
  于是,我试了下,AC了。
  然而,我不知道如何证明它的正确性!
  在实现上,直接模拟冒泡排序是不行的,会超时。我用了三种不同的实现方式,但是思路都是一样的。
  1、树状数组(参考《挑战程序设计竞赛》P178)

  在这个题目中,需要做的是,

  (1)自左向右把序列添加到树状数组中。注意,添加到树状数组中的不是序列的值,而是把序列值当作插入树状数组时的下标,插入的值为1。比如,在处理$a[i]$之前,$a[1]$~$a[i - 1]$已经处理完了。如果$a[j]$<$a[i]$($1 \le j<i$),在树状数组的下标中$a[j]$必定出现在$a[i]$的前面,所以,只要统计$[1, i - 1]$区间内比$a[i]$小或者等于的数字个数,就可以知道$[1, i - 1]$区间内比$a[i]$小的数字个数了。因为这里统计涉及到求前缀和,所以这就是为什么用树状数组的原因。

  (2)自右向左把序列添加到树状数组中。这样可以统计出$a[i]$右边比$a[i]$小的元素的个数。注意,统计比$a[i]$小的元素的个数,是计算$sum(a[i] - 1)$,而不是$sum(a[i])$,因为$a[i]$右边有可能有等于$a[i]$的数字存在。(其中,$sum(x)$表示树状数组中的$[1, x]$区间的前缀和)。

  还有两点需要注意:①这个题目中数值$0 \le a[i] \le 10^6$,而在树状数组中$0$是不能作为下标的。所以,在输入序列$a[i]$时,应把$a[i]$加一个正偏移量;②树状数组的最大长度是$max\{a_1, a_2, a_3, ……, a_n\} + 偏移量$,而不是$N$。

  
  2、归并排序
  对于归并排序求逆序数是没什么好说的。但是在这个题中,求的不是整个序列的逆序数。对于数字$a[i]$, 在归并排序的过程中,可以统计出位置在$a[i]$左边且数值比$a[i]$大的数字的个数。但是,特别需要注意的一点是,在统计个数的过程中,整个序列也在做排序,数字的位置变化了。再统计位置在它$a[i]$右边且数值比$a[i]$小的数字的个数时,没法做。所以,为了解决这个问题,需要把数值和计数器绑定成结构体,同时记录每个数字的初始顺序,把序列”打回原形“,再反向归并统计,以保证统计结果的正确性。
  关于如何统计位置在它$a[i]$右边且数值比$a[i]$小的数字的个数这个问题,做法和方法1类似,在归并有序的子序列时,下标从右向左遍历。不过,这时需要注意两点:①右子序列的遍历”指针“最小值是$mid + 1$,而不是$mid$;②数值比$a[i]$小的元素个数计算方法和”从左向右归并“不相同。
 
  3、可持久化线段树+二分(这个方法时间复杂度是$O(N*logN*logN)$,会超时。这里的实现只是纯属个人兴趣)
  对于序列中数字$a[i]$,二分一个数字$k$,使用可持久化线段树(俗称主席树)查询出$[1, i-1]$区间内第$k$小的数字,然后和$a[i]$比较,并调整二分边界;通过二分可以计算出$[1, i-1]$区间内比$a[i]$大的元素个数。同样,再次二分,也可以计算出$[i + 1, n]$区间内比$a[i]$小的元素个数。
 
二、源代码
  1、树状数组
#include<bits/stdc++.h>
using namespace std;
#define MAXV 1000010
#define MAXN 100010
typedef long long LL;
LL n, a[MAXN], cnt[MAXN], bits[MAXV], maxv;

template<class T> inline void read(T& x) {
    char t;
    bool sign = false;
    while((t = getchar()) != '-' && (t < '0' || t > '9'));
    if(t == '-')sign = true, t = getchar();
    x = t - '0';
    while((t = getchar()) >= '0' && t <= '9')x = x * 10 + t - '0';
    if(sign)x = -x;
}

LL sum(int x) {
    LL res = 0;
    for(int i = x; i > 0; i -= i & -i)res += bits[i];
    return res;
}

void add(LL x, LL val) {
    for(; x <= maxv; x += x & -x)bits[x] += val;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    while(~scanf("%I64d", &n)) {
        memset(cnt, 0, sizeof(cnt));
        maxv = 0;
        for(int i = 1; i <= n; ++i) {
            read(a[i]);
            ++a[i];
            if(a[i] > maxv)maxv = a[i];
        }

        memset(bits, 0, sizeof(bits));
        for(int i = 1; i <= n; ++i) {
            cnt[i] += (i - 1) - sum(a[i]);
            add(a[i], 1);
        }

        memset(bits, 0, sizeof(bits));
        for(int i = n; i > 0; --i) {
            cnt[i] += sum(a[i] - 1);
            add(a[i], 1);
        }

        LL ans = 0;
        for(int i = 1; i <= n; ++i)ans += (1LL + cnt[i]) * cnt[i] / 2LL;
        printf("%I64d\n", ans);
    }
    return 0;
}

 

  2、归并排序
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100010
typedef long long LL;
LL n;
struct Node {
    LL order, val, cnt;
} nodes[MAXN], tmp[MAXN];

template <class T> inline void read(T &x) {
    int t;
    bool flag = false;
    while((t = getchar()) != '-' && (t < '0' || t > '9')) ;
    if(t == '-') flag = true, t = getchar(); x = t - '0';
    while((t = getchar()) >= '0' && t <= '9') x = x * 10 + t - '0';
    if(flag) x = -x;
}


void merge_sort(int start, int end, bool dir) {
    int mid = (start + end) >> 1;
    if(end > start + 1) { //end - start + 1 > 2
        merge_sort(start, mid, dir);
        merge_sort(mid + 1, end, dir);
    }

    if(dir) {
        int pl = start, pr = mid + 1;
        for(int i = start; pl <= mid || pr <= end; ++i) {
            if(pl > mid)tmp[i] = nodes[pr++];
            else if(pr > end)tmp[i] = nodes[pl++];
            else if(nodes[pl].val <= nodes[pr].val)tmp[i] = nodes[pl++];
            else if(nodes[pl].val > nodes[pr].val) {
                nodes[pr].cnt += mid - pl + 1;
                tmp[i] = nodes[pr++];
            }
        }
    }
    else {
        int pl = mid, pr = end;
        for(int i = end; pl >= start || pr >= mid + 1; --i) {
            if(pr < mid + 1)tmp[i] = nodes[pl--];
            else if(pl < start)tmp[i] = nodes[pr--];
            else if(nodes[pr].val >= nodes[pl].val)tmp[i] = nodes[pr--];
            else if(nodes[pr].val < nodes[pl].val) {
                nodes[pl].cnt += pr - mid;
                tmp[i] = nodes[pl--];
            }
        }
    }
    memcpy(nodes + start, tmp + start, sizeof(nodes[start]) * (end - start + 1));
}

bool cmp(Node& n1, Node& n2) {
    return n1.order < n2.order;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    while(~scanf("%I64d", &n)) {
        memset(nodes, 0, sizeof(nodes));
        for(int i = 1; i <= n; ++i)read(nodes[i].val), nodes[i].order = i;
        merge_sort(1, n, true);
        sort(nodes + 1, nodes + n + 1, cmp);
        merge_sort(1, n, false);
        LL ans = 0;
        for(int i = 1; i <= n; ++i)ans += (1LL + nodes[i].cnt) * nodes[i].cnt / 2LL;
        printf("%I64d\n", ans);
    }
    return 0;
}

 

  3、可持久化线段树+二分
#include<bits/stdc++.h>
using namespace std;
#define MAXV 1000010
#define MAXN 100010
typedef long long LL;
LL n, a[MAXN], b[MAXN], buf[MAXN], cnt[MAXN];

template<class T> inline void read(T& x) {
    char t;
    bool sign = false;
    while((t = getchar()) != '-' && (t < '0' || t > '9'));
    if(t == '-')sign = true, t = getchar();
    x = t - '0';
    while((t = getchar()) >= '0' && t <= '9')x = x * 10 + t - '0';
    if(sign)x = -x;
}

void discrete(LL* bef, LL* aft) {
    sort(buf + 1, buf + n + 1);
    int m = unique(buf + 1, buf + n + 1) - buf;
    for(int i = 1; i <= n; ++i)aft[i] = lower_bound(buf + 1, buf + m + 1, bef[i]) - buf;
}

typedef struct {
    int root[MAXN], ncnt;
    struct seg {
        int lch, rch, cnt;
    } segs[MAXN * 20];
    void clear() {
        memset(segs, 0, sizeof(segs));
        memset(root, 0, sizeof(root));
        ncnt = 0;
    }
    void update(int& croot, int proot, int val, int l, int r) {
        if(croot == 0) {
            croot = ++ncnt;
            segs[croot].cnt = segs[proot].cnt + 1;
        }
        if(l == r)return;
        int mid = (l + r) >> 1;
        if(val <= mid) {
            segs[croot].rch = segs[proot].rch;
            update(segs[croot].lch, segs[proot].lch, val, l, mid);
        }
        else {
            segs[croot].lch = segs[proot].lch;
            update(segs[croot].rch, segs[proot].rch, val, mid + 1, r);
        }
    }
    int query(int qlr, int qrr, int k, int l, int r) {
        if(l == r)return l;
        int mid = (l + r) >> 1;
        if(k <= segs[segs[qrr].lch].cnt - segs[segs[qlr].lch].cnt) {
            return query(segs[qlr].lch, segs[qrr].lch, k, l, mid);
        }
        else {
            return query(segs[qlr].rch, segs[qrr].rch, k - (segs[segs[qrr].lch].cnt - segs[segs[qlr].lch].cnt),
                         mid + 1, r);
        }
    }
} PerSegTree;

PerSegTree pst;
int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    while(~scanf("%I64d", &n)) {
        pst.clear();
        memset(cnt, 0, sizeof(cnt));
        for(int i = 1; i <= n; ++i)read(a[i]), buf[i] = a[i];
        discrete(a, b);
        for(int i = 1; i <= n; ++i)pst.update(pst.root[i], pst.root[i - 1], int(b[i]), 1, n);
        for(int i = 2; i <= n; ++i){
            int low = 0, high = i, mid, num;
            while(low < high - 1){
                mid = (low + high) >> 1;
                num = pst.query(pst.root[0], pst.root[i - 1], mid, 1, n);
                if(num <= b[i])low = mid;
                else high = mid;
            }
            cnt[i] += i - 1 - low;
        }
        for(int i = 1; i < n; ++i){
            int low = 0, high = n - i + 1, mid, num;
            while(low < high - 1){
                mid = (low + high) >> 1;
                num = pst.query(pst.root[i], pst.root[n], mid, 1, n);
                if(num < b[i])low = mid;
                else high = mid;
            }
            cnt[i] += low;
        }

//        for(int i = 1; i <= n; ++i)printf("%I64d ", cnt[i]); printf("\n");
        LL ans = 0;
        for(int i = 1; i <= n; ++i)ans += (1LL + cnt[i]) * cnt[i] / 2LL;
        printf("%I64d\n", ans);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/fuzhihong0917/p/8487919.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值