【问题标题】:Efficient counting of occurrences in a range有效计数范围内的发生次数
【发布时间】:2022-01-20 20:22:20
【问题描述】:

您希望在处理学生成绩的同时收集信息。学校学生的默认列表编号为“1、2、3、4...”,程序必须处理以下指令:

  1. REGISTER (c):注册列表中的下一个学生获得的成绩c
  2. COUNT (c, i, j):统计在[i, j]之间有多少个成绩为c的学生(含)。

输入:
一个整数 N,后跟要处理的 N 条指令。您可以假设 0 ,成绩在 0 到 100 之间,所有列表编号范围都将引用已注册的学生。

输出:
每条 COUNT 指令的对应值。

示例:

  • 输入:

    7
    REGISTER 8
    REGISTER 7
    REGISTER 8
    COUNT 8 1 2
    COUNT 8 1 3
    REGISTER 7
    COUNT 7 1 2
    
  • 输出:

    1
    2
    1
    

您可以想象,这是一个互联网问题 (this one),我想出了这个解决方案:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    std::vector<short> A;
    int N;

    std::cin >> N;
    while (N--) {
        std::string W;

        std::cin >> W;
        if (W == "REGISTER") {
            short C;

            std::cin >> C;
            A.push_back(C);
        } else {
            int I, J;
            short C;

            std::cin >> C >> I >> J;
            std::cout << std::count(A.begin() + I - 1, A.begin() + J, C)
                      << "\n";
        }
    }

    return 0;
}

显然我的代码很慢。有人可以帮我找到更有效的解决方案吗?想了很久,还是没找到办法。

【问题讨论】:

  • 您的代码具有为竞争性编码网站编写的所有特征(请注意,您会在这些网站上养成一些非常糟糕的习惯)。这些是不需要的:std::ios_base::sync_with_stdio(false);、std::ios_base::sync_with_stdio(false);最后,不应在程序的内部循环中进行计数。竞争网站的另一个问题是,它们在您无法控制的系统上运行您的代码,因此所有时间都非常不可靠,不应过分信任。总而言之,如果您想学习 C++,请获得一本好书或访问 learncpp.com 之类的网站
  • @PepijnKramer 我从来没有听说过那个页面,我只是回顾了它,它对我来说很完美。谢谢。
  • 嘿,很高兴听到这个消息!我有一半害怕我用我的 cmets 吓到你(但解决问题和编写 C++ 是有区别的);)哦,如果你有问题,或者问题发布一个问题,我们会提供帮助

标签: c++ algorithm


【解决方案1】:

决定为您实施 3 个解决方案。第一个SolveSimple()和你的一样,我做它供参考以测试另外两个的正确性。第二个SolveBinSearch() 基于Binary Search 方法。第三个SolveDynProg() 基于Dynamic Programming 方法。

简单的解决方案(和你的一样)比其他两个慢大约10x 倍。二进制搜索大约比动态编程变体快2x。二进制搜索变体也比 dyn prog 更节省内存,但对于 100 K 指令的情况,这种内存经济性并不是一个大问题。

我的代码中的测试是随机生成的。您可以调整测试指令num_instructionsmax_grade 的数量,还可以标记verbose 控制是否输出额外的调试信息,例如每种方法的答案。在代码之后你会发现时间。

三个方法都接受输入和输出流,你可以传入标准输入和输出,如SolveBinSearch(std::cin, std::cout);,在将解决方案发送到在线比赛评委系统的情况下。

如果要选择,那么SolveBinSearch() 是最快且节省内存的解决方案。

Try it online!

#include <random>
#include <sstream>
#include <string>
#include <vector>
#include <cstdint>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <array>
#include <chrono>

using std::size_t;
using u8 = uint8_t;
using u32 = uint32_t;

void SolveSimple(std::istream & inp, std::ostream & out) {
    std::vector<u8> grades;
    while (inp) {
        std::string cmd;
        inp >> cmd;
        if (cmd == "REGISTER") {
            size_t grade = 0;
            inp >> grade;
            grades.push_back(grade);
        } else if (cmd == "COUNT") {
            size_t grade = 0, i = 0, j = 0;
            inp >> grade >> i >> j;
            out << std::count(grades.begin() + (i - 1),
                grades.begin() + j, grade) << std::endl;
        } else
            break;
    }
}

void SolveBinSearch(std::istream & inp, std::ostream & out) {
    // https://en.wikipedia.org/wiki/Binary_search_algorithm
    std::vector<std::vector<u32>> cnts(101);
    size_t cnt = 0;
    while (inp) {
        std::string cmd;
        inp >> cmd;
        if (cmd == "REGISTER") {
            size_t grade = 0;
            inp >> grade;
            cnts[grade].push_back(cnt);
            ++cnt;
        } else if (cmd == "COUNT") {
            size_t grade = 0, i = 0, j = 0;
            inp >> grade >> i >> j;
            auto begin = std::lower_bound(
                cnts[grade].begin(), cnts[grade].end(), i - 1);
            auto end = std::upper_bound(
                cnts[grade].begin(), cnts[grade].end(), j - 1);
            out << (end - begin) << std::endl;
        } else
            break;
    }
}

void SolveDynProg(std::istream & inp, std::ostream & out) {
    // https://en.wikipedia.org/wiki/Dynamic_programming
    std::vector<std::array<u32, 101>> cnts(1);
    while (inp) {
        std::string cmd;
        inp >> cmd;
        if (cmd == "REGISTER") {
            size_t grade = 0;
            inp >> grade;
            cnts.push_back(cnts.back());
            ++cnts.back()[grade];
        } else if (cmd == "COUNT") {
            size_t grade = 0, i = 0, j = 0;
            inp >> grade >> i >> j;
            out << cnts[j][grade] - cnts[i - 1][grade] << std::endl;
        } else
            break;
    }
}

void Test() {
    bool constexpr verbose = 0;
    size_t constexpr num_instructions = 100000, max_grade = 100;

    auto TimeCur = []{
        return std::chrono::high_resolution_clock::now();
    };
    auto const gtb = TimeCur();
    auto Time = [&]{
        return std::chrono::duration_cast<std::chrono::microseconds>(
            TimeCur() - gtb).count() / 1000000.0;
    };

    std::mt19937_64 rng{123};
    std::stringstream inp;
    size_t cnt = 0;
    for (size_t i = 0; i < num_instructions; ++i) {
        if ((rng() & 1) || (cnt == 0)) {
            ++cnt;
            inp << "REGISTER " << rng() % (max_grade + 1) << std::endl;
        } else {
            size_t a = rng() % cnt + 1, b = rng() % cnt + 1;
            if (a > b)
                std::swap(a, b);
            inp << "COUNT " << rng() % (max_grade + 1) << " "
                << a << " " << b << std::endl;
        }
    }
    auto inp_str = inp.str();
    if (verbose) {
        std::cout << "Input: " << std::endl
            << inp.str() << std::endl;
    }

    std::cout << std::fixed << std::boolalpha;
    double tb = 0;
    std::stringstream out_simple, out_binsearch, out_dynprog;

    tb = Time(); inp.clear(); inp.str(inp_str);
    SolveSimple(inp, out_simple);
    std::cout << "Simple time " << (Time() - tb) << std::endl;

    tb = Time(); inp.clear(); inp.str(inp_str);
    SolveBinSearch(inp, out_binsearch);
    std::cout << "BinSearch time " << (Time() - tb) << std::endl;

    tb = Time(); inp.clear(); inp.str(inp_str);
    SolveDynProg(inp, out_dynprog);
    std::cout << "DynProg time " << (Time() - tb) << std::endl;

    if (verbose) {
        std::cout << "Simple: " << std::endl
            << out_simple.str() << std::endl;
        std::cout << "BinSearch: " << std::endl
            << out_binsearch.str() << std::endl;
        std::cout << "DynProg: " << std::endl
            << out_dynprog.str() << std::endl;
    }
    std::cout << "Simple == BinSearch: "
        << (out_simple.str() == out_binsearch.str()) << std::endl;
    std::cout << "Simple == DynProg: "
        << (out_simple.str() == out_dynprog.str()) << std::endl;
    std::cout << "BinSearch == DynProg: "
        << (out_binsearch.str() == out_dynprog.str()) << std::endl;
}

int main() {
    Test();
}

输出:

Simple time 0.184678
BinSearch time 0.020600
DynProg time 0.048994
Simple == BinSearch: true
Simple == DynProg: true
BinSearch == DynProg: true

【讨论】:

  • 非常感谢。我一直在研究所有三种解决方案,当然我坚持使用二进制搜索实现,但动态解决方案对我来说似乎也很有趣
  • @DiegoFC 如果我的解决方案很有趣和/或正确,那么不要忘记支持它和/或接受它。 UpVoting 在我的答案顶部完成,通过单击上三角形(箭头)在它的左侧。通过单击投票地点附近的复选标记来完成接受。接受意味着答案对你来说是正确的,它解决了任务。 UpVoting 表示您喜欢解决方案并希望给作者(我)额外的分数。接受和赞成可以同时进行。这两个动作都给了我额外的分数来提高我的声誉。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-06
  • 2013-07-18
  • 2017-03-19
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 2020-11-09
相关资源
最近更新 更多