HDU 1394求逆序数(暴力和线段树双解)

觉得开始需要着手练下线段树了,所以从单点更新开始着手。

题目链接:HDU 1394.Minimum Inversion Number

题目大意:给定一个长度为n的序列,然后每次将序列当中的第一个数放在序列末尾,这样我们一共会得到n个序列。每个序列都会有个逆序数,取这n个数当中的最小的作为结果输出。

最最暴力的方法是把这n种情况排列出来,然后每种情况都算一下逆序数,然后取最小。

由于暴力算法计算一组情况逆序数的时间复杂度是O(n^2)的,n为小于等于5000的数,所以我们可以考虑下能否用一组逆序数推出其他所有情况逆序数的结果呢?由于本题的特殊性,是可以做到的,首先n个数中分别是[0, n - 1]当中的数字而且无重复数字出现,那么我们假设已知的某一序列的逆序数为sum,那么它的下一个序列式将a0放到队尾,那么序列中的其他数字必然都在它的前面(包括a0个比它小的数和n - a0 - 1个比它大的数),也就是说逆序数sum会减少a0个,增加n - a0 - 1个。

即sum = sum - (a0) + (n - a0 - 1);

暴力O(n^2)做法代码:(218ms)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[5010];
int n;
int main() {
    while(~scanf("%d", &n)) {
        for(int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        int ans = 1e9;
        int x = 0;
        for(int i = 0; i < n; i++) {
            for(int j = i + 1; j < n; j++) {
                if(a[i] > a[j]) x++;
            }
        }
        for(int i = 0; i < n; i++) {
            x = x + (n - a[i] - 1) - (a[i]);
            ans = min(ans, x);
        }
        printf("%d\n", ans);
    }
    return 0;
}
线段树做法的核心部分和暴力法相同,也是只构建一棵空树,按照输入的顺序更新和查询加和。

线段树:(78ms) 刚开始写,线段树的格式还有待学习和习惯,虽然格式被吐槽了。。不过还是觉得封装起来舒服

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAX 5010
#define LL(x) (x << 1)
#define RR(x) (x << 1 | 1)
#define MID(x, y) (x + ((y - x) >> 1))
int n;
int a[MAX];
class TNode {
    public:
    int l, r, num;
    int mid() {return MID(l, r);}
    void init() {num = 0; return ;}
};
class segment_tree {
    public:
    TNode tree[4 * MAX];
    void build(int l, int r, int t) {
        tree[t].l = l, tree[t].r = r;
        tree[t].init();
        if(l != r) {
            int mid = tree[t].mid();
            build(l, mid, LL(t));
            build(mid + 1, r, RR(t));
        }
    }
    void update(int pos, int t) {
        int l = tree[t].l, r = tree[t].r;
        if(l == r) tree[t].num++;
        else {
            int mid = tree[t].mid();
            if(pos <= mid) update(pos, LL(t));
            else update(pos, RR(t));
            tree[t].num = tree[LL(t)].num + tree[RR(t)].num;
        }
    }
    int query(int st, int ed, int t) {
        int l = tree[t].l, r= tree[t].r;
        if(st <= l && ed >= r) return tree[t].num;
        else {
            int mid = tree[t].mid();
            int num1 = 0, num2 = 0;
            if(st <= mid) num1 = query(st, ed, LL(t));
            if(ed > mid) num2 = query(st, ed, RR(t));
            return num1 + num2;
        }
    }
}seg;
int main() {
    while(~scanf("%d", &n)) {
        seg.build(0, n - 1, 1);
        int sum = 0;
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            seg.update(a[i], 1);
            if(a[i] != n - 1) sum += seg.query(a[i] + 1, n - 1, 1);
        }
        int ans = 1e9;
        for(int i = 1; i <= n; i++) {
            sum = sum + (n - a[i] - 1) - (a[i]);
            ans = min(ans, sum);
        }
        printf("%d\n", ans);
    }
    return 0;
}



转载于:https://www.cnblogs.com/gaoxiang36999/p/4451516.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值