zoukankan      html  css  js  c++  java
  • AcWing 362. 区间

    听书上说有贪心 + 数据结构的做法,研究了一下。

    朴素贪心

    考虑把所有线段按照右端点 (b) 从小到大排序,依次考虑每一条线段的要求:

    • 如果已经满足要求则跳过
    • 否则尽量选择靠后的数(因为之后的线段的右端点都在这条线段的右边,这样容错更高)

    所以,我们可以建一个数组,(d[i]) 表示 (i) 数字是否选择(填(1)(0)),扫一遍 ([l, r]) 区间求和,然后从后往前贪心放数即可。

    对于每条线段需要 (O(r - l + 1))。所以最坏情况下 (O(n ^ 2))。但是轻松 (52ms) 过了。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 50005;
    int n, d[N], c[N];
    struct Seg{
        int a, b, c;
        bool operator < (const Seg &x) const {
            return b < x.b;
        }
    }e[N];
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
        sort(e + 1, e + 1 + n);
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            int l = e[i].a, r = e[i].b, cnt = e[i].c;
            for (int j = l; j <= r; j++)
                cnt -= d[j];
            if(cnt > 0) {
                for (int j = r; j >= l && cnt; j--)
                    if(!d[j]) cnt--, ans++, d[j] = 1;
            }
        }
        printf("%d
    ", ans);
        return 0;
    }
    

    优化

    考虑用数据结构优化。

    发现我们需要三个操作:

    • 询问 ([l, r]) 区间的数字个数
    • 将值为 (x) 的位置 (+1)
    • 从后往前,找到比当前位置靠前的下一个 (0) 的位置。
    1. 前两个就是 “区间求和,单调修改”,典型的树状数组。$O(nlog_250000) $

    2. 第三种操作,可以用并查集优化。为什么可以确保时间复杂度呢?对于每一条线段,最多只有一次会枚举到 (1) (即开始的那一次),之后每次枚举都会枚举到 (0) 的位置,即(d[i] = 0),然后把它变成 (1),而以后就不会访问到了。而一共有 (50000) 个值,所以复杂度是 (O(50000log_n))

    (33ms)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 50001;
    int n, d[N], c[N], f[N];
    struct Seg{
        int a, b, c;
        bool operator < (const Seg &x) const {
            return b < x.b;
        }
    }e[N];
    // 树状数组
    int inline ask(int x) {
        int res = 0;
        for (; x; x -= x & -x) res += c[x];
        return res;
    }
    
    void inline add(int x) {
        for (; x < N; x += x & -x) c[x]++;
    }
    // 并茶集:find(x) 表示找到 <= x 中最大的一个是 0 的数
    int find(int x) {
        return x == f[x] ? x : f[x] = find(f[x]);
    }
    int main() {
        scanf("%d", &n);
        for (int i = 0; i < N; i++) f[i] = i;
        for (int i = 1; i <= n; i++) 
            scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
        sort(e + 1, e + 1 + n);
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            int l = e[i].a, r = e[i].b, cnt = e[i].c;
            // 取 [l, r] 选了多少个数
            cnt -= ask(r) - ask(l - 1);
            if(cnt > 0) {
                for (int j = r; j >= l && cnt; ) {
                    // d[j] == 1 的情况每条线段至多出现一次
                    if(!d[j]) {
                        cnt--, ans++, d[j] = 1;
                        // j 被标记成 1 了,要指向 find(j - 1)
                        f[j] = j - 1;
                        // 维护树状数组
                        add(j);
                    };
                    if(find(j) != j) j = f[j];
                    else j--;
                }
            }
        }
        printf("%d
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    数据库复习之数据库系统概论
    C++前缀表达式和后缀表达式
    MySQL——排序和分页
    MySQL——联表查询
    MySQL——Where条件子句
    MySQL——DQL查询数据(Select)
    MySQL——外键、DML语言(添加、修改、删除)
    MySQL——数据库的操作、属性
    MySQL——简介和安装
    MySQL——数据库
  • 原文地址:https://www.cnblogs.com/dmoransky/p/11923718.html
Copyright © 2011-2022 走看看