挂分启示录

1. 关于 returnstd::exit()

main 函数以 return 语句或以抵达函数尾返回,会进行正常函数终止(调用拥有自动存储期的对象的析构函数),再执行 std::exit ,将 return 语句的参数(或如果使用隐式 return 就是 0 )作为 exit_code 传递。

也就是说 return 语句相比 std::exit 函数多执行了一步 调用拥有自动存储期的对象的析构函数。在使用了大量 STL 容器的代码中,正是多执行的这一步可能会造成 TLE,形成输出了结果但是超时的情况。

附:AC CodeTLE Code

2. 关于 std::liststd::vector 在图的存储方面表现

经过实际测试,可以发现在图的存储方面 std::vector 要优于 std::list,无论是否调用析构函数。原因猜测为在边的数量众多的情况下使用 std::list 会造成大量内存碎片,导致运行缓慢。

附:vector + 析构函数list + 析构函数vector - 析构函数list - 析构函数

3. 关于 std::dequestd::vector 的最小内存开销

std::deque 的存储按需自动扩展及收缩。扩张 std::deque 比扩张 std::vector 更优,因为它不涉及到复制既存元素到新内存位置。另一方面,std::deque 典型地拥有较大的最小内存开销;只保有一个元素的 std::deque 必须分配它的整个内部数组(例如 64 位 libstdc++ 上是对象尺寸的 8 倍;64 位 libc++ 上是对象尺寸的 16 倍和 4096 字节中的较大者)。

也就是说在需要动态的存储多种元素但每种元素数量相对较少的时候,使用 std::vectorstd::deque 在空间上更优。

附:std::dequestd::vector

4. 关于 long double 和浮点数极大值

根据实际测试,在浮点数运算中若使用 infinity 赋极大值会显著降低运算速度,下表为 Windows 环境下进行 \(2 \times 10^7\) 次运算操作的用时:

数值 float double long double
std::numeric_limits<>::max() \(\tt{103ms}\) \(\tt{115ms}\) \(\tt{101ms}\)
std::numeric_limits<>::infinity() \(\tt{100ms}\) \(\tt{109ms}\) \(\tt{4056ms}\)

测试程序如下:

Code
#include<bits/stdc++.h>

int main() {
    std::cout << "float" << std::endl;

    std::cout << std::numeric_limits<float>::max() << std::endl;
    std::cout << std::numeric_limits<float>::min() << std::endl;
    std::cout << std::numeric_limits<float>::infinity() << std::endl;

    std::cout << "double" << std::endl;

    std::cout << std::numeric_limits<double>::max() << std::endl;
    std::cout << std::numeric_limits<double>::min() << std::endl;
    std::cout << std::numeric_limits<double>::infinity() << std::endl;

    std::cout << "long double" << std::endl;

    std::cout << std::numeric_limits<long double>::max() << std::endl;
    std::cout << std::numeric_limits<long double>::min() << std::endl;
    std::cout << std::numeric_limits<long double>::infinity() << std::endl;

    std::cout << "===== speed test =====" << std::endl;

    constexpr int T = 20000000;

    std::cout << "float + max" << std::endl;

    {
        float f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<float>::max() / f;

            if (f < std::numeric_limits<float>::max())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    std::cout << "float + infinity" << std::endl;

    {
        float f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<float>::infinity() / f;

            if (f < std::numeric_limits<float>::infinity())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    std::cout << "double + max" << std::endl;

    {
        double f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<double>::max() / f;

            if (f < std::numeric_limits<double>::max())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    std::cout << "double + infinity" << std::endl;

    {
        double f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<double>::infinity() / f;

            if (f < std::numeric_limits<double>::infinity())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    std::cout << "long double + max" << std::endl;

    {
        float f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<float>::max() / f;

            if (f < std::numeric_limits<float>::max())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    std::cout << "long double + infinity" << std::endl;

    {
        long double f = 1;

        auto start = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < T; ++i) {
            f += std::numeric_limits<long double>::infinity() / f;

            if (f < std::numeric_limits<long double>::infinity())
                f /= 2;
        }

        auto end = std::chrono::high_resolution_clock::now();

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
    }

    return 0;
}

5. 关于隐式类型转换

cppreference 所述,各类型在运算中转换的优先级从低到高按如下所示:

  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long

因此若一个 unsigned long long 类型与一个 long long 类型的元素作运算且结果可能为负,那么会自动转化为 unsigned long long 类型并对 \(2^{64}\) 自动取模。

posted @ 2023-08-16 14:54  User-Unauthorized  阅读(132)  评论(5编辑  收藏  举报