算法竞赛进阶指南0x05 排序

在程序设计中,通常会使用到以下这些排序算法,这里把它们分为三类:

1.选择排序、插入排序、冒泡排序

2.堆排序、归并排序、快速排序

3.计数排序、基数排序、桶排序

$$ 前两类是基于比较的排序算法,对 n 个元素进行排序时$$ $$ 若元素比较大小的时间复杂度为O(1),则第一类排序算法的时间复杂度为O(n^2) $$ $$ 第二类排序算法的时间复杂度为 O(n logn)。实际上,基于比较的排序算法的时间复杂度下界为 O(nlogn) $$ $$ 因此堆排序、归并排序、快速排序已经是时间复杂度最优的基于比较的排序算法。 $$

离散化

离散化,就是当我们只关心数据的大小关系时,用排名代替原数据进行处理的一种预处理方法。离散化本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数。当原数据很大或含有负数、小数时,难以表示为数组下标,一些算法和数据结构(如BIT)无法运作,这时我们就可以考虑将其离散化。

实际上,离散化可以用STL较简单地完成。

例如,现在我们有序列A=[10, 23, 35, 3, -40, 3]。我们先复制一个同样的序列:

int C[MAXN];
memcpy(C, A, sizeof(A));

排序,去重:

sort(C, C + n);
int l = unique(C, C + n) - C; // l为不重复元素的数量

std::unique()的返回值是一个迭代器(对于数组来说就是指针了),它表示去重后容器中不重复序列的最后一个元素的下一个元素。所以可以这样作差求得不重复元素的数量。现在我们有C=[-40, 3, 10, 23, 35]。

再用一个数组,储存A中每个元素在C中的排名:

int L[MAXN];
for (int i = 0; i < n; ++i)
    L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 二分查找

这样我们就实现了原序列的离散化。得到L = [3,4,5,2,1,2]。

$$ 因为排序和 n 次二分查找的复杂度都是 O(n logn),所以离散化的时间复杂度也是O(n log n)。$$ 完整代码很短: 

int C[MAXN], L[MAXN];
// 在main函数中...
memcpy(C, A, sizeof(A)); // 复制
sort(C, C + n); // 排序
int l = unique(C, C + n) - C; // 去重
for (int i = 0; i < n; ++i)
    L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 查找

$$ 离散化也不一定要从小到大排序,有时候也需要从大到小 $$

$$ 这时在排序和查找时相应地加上 greater<int>() 就可以了 $$

例题:程序自动分析

tag:离散化,并查集,排序        

思路:对于"变量相等"的约束条件,我们将其放入同一个集合里面,对于"变量不相等"的约束条件,我们看两个元素是否在同一个集合里面(根相同),如果在同一个集合里面,无法满足既相等要不相等的互斥条件,所以我们要输出NO,全部都不冲突的话,我们输出YES。但是我们要注意:

要先把所有的"变量相等"的数据排在前面,最后判断不相等的情况,其次,因为x和y的数据范围太大,无法开那么大的fa数组,所以我们要对数据离散化处理。具体代码如下:

#include <bits/stdc++.h>
#define i64 long long

constexpr int N = 1e6;

int fa[N];

int find(int x) {
    return fa[x] == x ? x : (fa[x] = find(fa[x]));
}

void merge(int x, int y) {
    fa[find(x)] = find(y);
}

struct node {
    int x, y, opt;
};

bool cmp(node a, node b) {
    return a.opt > b.opt;
}//排序

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t;
    std::cin >> t;
    while (t--) {

        int n;
        std::cin >> n;
        std::vector<node> a(n + 1);
        std::vector<int> POS;

        std::memset(fa, 0, sizeof fa);

        for (int i = 1; i <= n; i++) {
            std::cin >> a[i].x >> a[i].y >> a[i].opt;
            POS.emplace_back(a[i].x);
            POS.emplace_back(a[i].y);
        }

        std::sort(POS.begin(), POS.end());

        POS.erase(std::unique(POS.begin(), POS.end()), POS.end());

        for (int i = 1; i <= n; i++) {
            a[i].x = 
        std::lower_bound(POS.begin(), POS.end(), a[i].x) - POS.begin();
            a[i].y = 
        std::lower_bound(POS.begin(), POS.end(), a[i].y) - POS.begin();
        }//离散化
        
        int cnt = (int)POS.size();

        for (int i = 1; i <= cnt; i++) {
            fa[i] = i;
        }//初始化

        std::sort(a.begin() + 1, a.end(), cmp);

        bool f = false;

        for (int i = 1; i <= n; i++) {
            if (a[i].opt == 1) {
                merge(a[i].x, a[i].y);
            } else {
                if (find(a[i].x) == find(a[i].y)) {
                    f = true;
                    break;
                    //冲突
                }
            }
        }
    
        if (f) {
            std::cout << "NO\n";
        } else {
            std::cout << "YES\n";
        }//注意大小写

    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值