(v4 更新)0x10 基础

0x11 基础 Misc

常用常数表

常用公式

日期公式

0x11 日期公式.cpp

// 返回从 1/1/1 到 y/m/d 的天数,由于 1/1/1 是周一,所以 mod 7 可以算出是周几
int getday(int y, int m, int d) {
    if (m < 3) y --, m += 12;
    return (365 * y + y / 4 - y / 100 + y / 400 + (153 * (m - 3) + 2) / 5 + d - 307);
}

// 返回从 1/1/1 开始经过 n 天的日期(getday 的逆函数)
std::array<int, 3> date(int n) {
    int y, m, d;
    n += 429 + ((4 * n + 1227) / 146097 + 1) * 3 / 4;
    y = (4 * n - 489) / 1461;
    n -= y * 1461 / 4;
    m = (5 * n - 1) / 153;
    d = n - m * 153 / 5;
    if (-- m > 12) m -= 12, y ++;
    return {y, m, d};
}

xorshift

0x11 xorshift.cpp:xorshift 是一种确定、线性、可逆的变换。常用于打乱键值(key)。

u64 mask = std::mt19937_64(std::random_device{}())();
u64 xorshift(u64 x) {
    x ^= mask;
    x ^= x << 13, x ^= x >> 7, x ^= x << 17;
    x ^= mask;
    return x;
}

xorsum

\[\operatorname{xor}_{i = 1}^n i = \begin{cases} n, & n \bmod 4 = 0 \\ 1, & n \bmod 4 = 1 \\ n + 1, & n \bmod 4 = 2 \\ 0, & n \bmod 4 = 3 \end{cases} \]

0x11 xorsum.cpp

int xorsum(int n) {
    if (n % 4 == 0) {
        return n;
    } else if (n % 4 == 1) {
        return 1;
    } else if (n % 4 == 2) {
        return n + 1;
    } else {
        return 0;
    }
}

Sum of Powers

\[\sum_{i = 1}^n i^2 = \frac{n(n + 1)(2n + 1)}{6}, \quad \sum_{i = 1}^n i^3 = \left( \frac{n(n + 1)}{2} \right)^2 \\ \sum_{i = 1}^n i^4 = \frac{n(n + 1)(2n + 1)(3n^2 + 3n - 1)}{30} \\ \sum_{i = 1}^n i^5 = \frac{n^2(n + 1)^2(2n^2 + 2n - 1)}{12} \]

常用配置

makefile

CXXFLAGS = -std=c++23 -O2 -Wall -fsanitize=undefined

# -D_GLIBCXX_DEBUG : STL debug mode
# -fsanitize=address : 内存错误检查
# -fsanitize=undefined : UB 检查

对拍脚本

需要 chmod +x cmp.bash,然后再 ./cmp.bash

while true; do
    ./gen >0.in
    ./obj <0.in >1.out
    ./std <0.in >2.out
    if diff 1.out 2.out; then
        echo ac
    else
        echo wa
        break;
    fi
done

随机数据生成

生成区间 \([l, r]\) 中的随机整数

0x11 生成区间 [l,r] 中的随机整数.cpp

std::mt19937_64 mtrand{std::random_device{}()}; // 若使用 testlib.h,将种子替换为 rnd.next()

// 生成区间 [l, r] 中的随机整数
int rand(int l, int r) {
    std::uniform_int_distribution<int> range(l, r);
    return range(mtrand);
}

生成区间 \([l, r)\) 中的随机浮点数

0x11 生成区间 [l,r) 中的随机浮点数.cpp

std::mt19937_64 mtrand{std::random_device{}()}; // 若使用 testlib.h,将种子替换为 rnd.next()

// 生成区间 [l, r) 中的随机浮点数
double rand(double l, double r) {
    std::uniform_real_distribution<double> range(l, r);
    return range(mtrand);
}

生成若干个互不相同的随机整数

0x11 生成若干个互不相同的随机整数.cpp生成方式非均匀随机。

// 生成 n 个在区间 [l, r] 中的互不相同的随机整数
auto GenSequence(int n, int l, int r) {
    assert(n <= r - l + 1);
    std::vector<int> res(n);
    for (int i = 0; i < n; i ++) {
        res[i] = rand(l, r - n + 1);
    }
    std::sort(res.begin(), res.end());
    for (int i = 0; i < n; i ++) {
        res[i] += i;
    }
    std::shuffle(res.begin(), res.end(), mtrand);
    return res;
}

生成随机区间列

0x11 生成随机区间列.cpp生成方式非均匀随机。

// 生成 m 个端点在 [L, R] 中的随机区间列
auto GenRanges(int m, int L, int R) {
    std::vector< std::pair<int, int> > res(m);
    for (int i = 0; i < m; i ++) {
        int l = rand(L, R), r = rand(L, R);
        if (l > r) {
            std::swap(l, r);
        }
        res[i] = {l, r};
    }
    return res;
}

生成随机父向树

  • 随机父向树的期望树高为 \(\mathcal{O}(\log n)\) 级别。

0x11 生成随机父向树.cpp

// 生成 n 个点的随机父向树
auto GenTree(int n) {
    std::vector< std::pair<int, int> > res;
    for (int i = 2; i <= n; i ++) {
        res.push_back({rand(1, i - 1), i});
    }
    return res;
}

生成随机树

  • 随机树的期望直径为 \(\mathcal{O}(\sqrt{n})\) 级别。

0x11 生成随机树.cpp

// 生成 n 个点的随机树
auto GenTree(int n) {
    std::vector< std::pair<int, int> > res;
    std::vector<int> prufer(n - 1), deg(n + 1, 1);
    for (int i = 1; i <= n - 2; i ++) {
        prufer[i] = rand(1, n);
        deg[prufer[i]] ++;
    }
    int leaf = 0, p = 0;
    for (int i = 1; i <= n; i ++) {
        if (deg[i] == 1) {
            leaf = p = i;
            break;
        }
    }
    for (int i = 1; i <= n - 2; i ++) {
        int x = prufer[i];
        res.push_back({leaf, x});
        if (-- deg[x] == 1 && x < p) {
            leaf = x;
        } else {
            p ++;
            while (deg[p] != 1) p ++;
            leaf = p;
        }
    }
    if (n > 1) {
        res.push_back({leaf, n});
    }
    return res;
}

常用库函数重载

__int128

0x11 __int128.cpp:重载了 __int128 的标准输入输出流。

using i128 = __int128;

// 重载 i128 标准输入流
std::istream &operator >> (std::istream &is, i128 &x) {
    std::string s;
    is >> s;
    int n = s.length(), f = s[0] == '-';
    x = 0;
    for (int i = f; i < n; i ++) {
        x = x * 10 + s[i] - '0';
    }
    if (f) x = -x;
    return is;
}

// 重载 i128 标准输出流
std::ostream &operator << (std::ostream &os, i128 x) {
    if (x < 0) x = -x, os << '-';
    std::string s;
    do {
        s += char(x % 10 + '0'), x /= 10;
    } while (x);
    std::reverse(s.begin(), s.end());
    return os << s;
}

取最值

0x11 取最值.cpp

// 取 min
template <class T>
inline void chmin(T &x, const T &y) {
    if (x > y) {
        x = y;
    }
}
// 取 max
template <class T>
inline void chmax(T &x, const T &y) {
    if (x < y) {
        x = y;
    }
}

取模

0x11 取模.cpp

const int mod = 998244353; // 模数需要根据实际问题调整

// 模意义下 修正
template <class T>
inline int norm(T x) {
    x %= mod;
    return x < 0 ? x + mod : x;
}

// 模意义下 加法
inline void add(int &x, const int &y) {
    x += y;
    if (x >= mod) {
        x -= mod;
    }
}
// 模意义下 减法
inline void dec(int &x, const int &y) {
    x -= y;
    if (x < 0) {
        x += mod;
    }
}
// 模意义下 取反
inline void neg(int &x) {
    if (x) {
        x = mod - x;
    }
}
// 模意义下 乘法
inline void mul(int &x, const int &y) {
    x = 1ll * x * y % mod;
}

// 快速幂
constexpr int qpow(int a, int b, int p) {
    int ans = 1;
    for (; b; b >>= 1) {
        if (b & 1) ans = 1ll * ans * a % p;
        a = 1ll * a * a % p;
    }
    return ans;
}

整数除法精确取整

0x11 整数除法精确取整.cpp

// 整数除法 精确上取整
i64 ceilDiv(i64 n, i64 m) {
    // assert(m);
    if (m < 0) n = -n, m = -m;
    if (n >= 0) {
        return (n + m - 1) / m;
    } else {
        return n / m;
    }
}
// 整数除法 精确下取整
i64 floorDiv(i64 n, i64 m) {
    // assert(m);
    if (m < 0) n = -n, m = -m;
    if (n >= 0) {
        return n / m;
    } else {
        return (n - m + 1) / m;
    }
}

精确开根号

0x11 精确开根号.cpp

// 精确开根号:找到最大整数 d,使得 d * d <= n
i64 isqrt(i64 n) {
    i64 d = sqrt(n);
    while (d * d > n) {
        d --;
    }
    while ((d + 1) * (d + 1) <= n) {
        d ++;
    }
    return d;
}

精确取对数

0x11 精确取对数.cpp

// 精确取对数(下取整):找到最大整数 t,使得 a^t <= b
int ilog(i64 a, i64 b) {
    int t = 0;
    i64 v = 1;
    while (v <= b / a) {
        t ++;
        v *= a;
    }
    return t;
}
// 精确取对数(上取整):找到最小整数 t,使得 a^t >= b
int iLog(i64 a, i64 b) {
    int t = 0;
    i64 v = 1;
    while (v < b) {
        t ++; if (v > b / a) break;
        v *= a;
    }
    return t;
}

字符串转数字

0x11 字符串转数字.cpp:可以配合 std::string 中的 substr 来提取一段子串所表示的数字。

// 字符串转数字
int digit(const std::string &s) {
    int x = 0;
    for (char ch : s) {
        x = x * 10 + ch - '0';
    }
    return x;
}

快读

0x11 快读.cpp

// 读入一个整数(无需在意类型)
template <class T>
inline void read(T &x) {
    static char s;
    static bool opt;
    while (s = getchar(), (s < '0' || s > '9') && s != '-');
    x = (opt = s == '-') ? 0 : s - '0';
    while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
    if (opt) x = -x;
}

超级快读

0x11 超级快读.cpp

/**
 * 注意,此代码的所有输出类函数,均不带“空格”与“换行”
 * 注意,虽然此代码使用了缓冲区,但不需要你手动清空,fastio 的析构函数会自动清空
 * 注意,此快读快写不能与 C/C++ 风格的 IO 混用,否则会导致 IO 的顺序混乱
 * 注意,如果你需要使用控制台调试,请在输入的最后使用 Ctrl+Z 手动输入 EOF 来结束读入
 */
struct fastio {
    static const int N = 1 << 20;

    char buf[N], *p1 = buf, *p2 = buf;
    #define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, N, stdin), p1 == p2) ? EOF : *p1 ++)

    char pbuf[N], *pp = pbuf;
    void pc(const char &c) {
        if (pp - pbuf == N) fwrite(pbuf, 1, N, stdout), pp = pbuf;
        *pp ++ = c;
    }

    // 读入一个整数(无需在意类型)
    template <class T>
    void read(T &x) {
        static char s;
        static bool opt;
        while (s = gc(), (s < '0' || s > '9') && s != '-');
        x = (opt = s == '-') ? 0 : s - '0';
        while (s = gc(), s >= '0' && s <= '9') x = x * 10 + s - '0';
        if (opt) x = -x;
    }

    // 输出一个整数(无需在意类型)
    template <class T>
    void print(T x) {
        int top = 0; static int stk[40];
        if (x < 0) x = -x, pc('-');
        do {
            stk[++ top] = x % 10, x /= 10;
        } while (x);
        while (top) pc(stk[top --] + '0');
    }

    // 输出一个字符串(char 数组类型)
    void puts(const char *s) {
        while (*s) pc(*s), s ++;
    }
    // 输出一个字符串(std::string 类型)
    void puts(const std::string &s) {
        for (char ch : s) pc(ch);
    }

    ~fastio() {
        fwrite(pbuf, pp - pbuf, 1, stdout);
    }
} io;

高精度

分数类

0x12 基础 算法

主定理

主定理:用于求解分治递归类型的渐进时间复杂度问题。

具体地,对于一个规模为 \(n\) 的问题,将其划分成 \(a\) 个规模为 \(\frac{n}{b}\) 的子问题,附带 "合并/分割" 的开销 \(f(n)\)

\[T(n) = aT\left( \frac{n}{b} \right) + f(n) \]

\(n^{\log_b a}\) 为处理问题用时的基准线。

  • \(f(n) = O(n^{\log_b a - \epsilon})\)
    直觉:总工作量由 "叶子层" 主导。

\[T(n) = \Theta(n^{\log_b a}) \]

  • \(f(n) = \Theta(n^{\log_b a}\log^k n)\) 且满足 \(k\geq 0\)
    直觉:每一层工作量相近,共 \(\log_b n\) 层。

\[T(n) = \Theta(n^{\log_b a}\log^{k + 1} n) \]

  • \(f(n) = \Omega(n^{\log_b a + \epsilon})\) 且满足正则性条件 \(af(\frac{n}{b}) \leq cf(n)\)(其中 \(c < 1\)
    直觉:总工作量由顶层的 合并/分割 主导。

\[T(n) = \Theta(f(n)) \]

一些例子:

  • 情形 1:遍历满二叉树 \(T(n) = 2T(\frac{n}{2}) + O(1) = O(n)\)
  • 情形 2:归并排序 \(T(n) = 2T(\frac{n}{2}) + O(n) = \mathcal{O}(n \log n)\)
  • 情形 3:多项式牛顿迭代 \(T(n) = T(\frac{n}{2}) + \mathcal{O}(n \log n) = \mathcal{O}(n \log n)\)

一个主定理处理不了的例子:\(T(n) = 2T\left(\frac{n}{2}\right) + \mathcal{O}\left(\frac{n}{\log n}\right) = \mathcal{O}(n \log \log n)\)

快速乘

求解 \(a \times b \bmod p\),其中 \(a, b, p\) 均为 long long 范围。

正确率正道更高,效率魔道更高。

正道:__int128

0x12 快速乘(__int128).cpp

// 快速乘(i128)
constexpr i64 qmul(i64 a, i64 b, i64 p) {
    return i128(a) * b % p;
}

魔道:long double

注意到 \(a\times b \bmod p = a\times b - \lfloor \frac{a \times b}{p} \rfloor \times p\),利用 long double 来处理 \(\lfloor \frac{a \times b}{p} \rfloor\)

模数较大时可能会出锅。

0x12 快速乘(long double).cpp

// 快速乘(long double)
constexpr i64 qmul(i64 a, i64 b, i64 p) {
    i64 c = static_cast<long double>(a) * b / p + 1e-8;
    i64 ans = a * b - c * p;
    if (ans < 0) ans += p;
    if (ans >= p) ans -= p;
    return ans;
}

光速幂

对于一个固定底数 \(a\),多次询问 \(a^i \bmod p\) 的值(其中 \(0 \leq i \leq n\))。

取阈值 \(B = \left\lceil \sqrt{n} \right\rceil\),先预处理出 \(a^1, a^2, \cdots, a^i, \cdots, a^B\),再预处理出 \(a^B, a^{2B}, \cdots, a^{iB}, \cdots, a^{\lfloor n / B \rfloor B}\)

每次只需利用 \(a^i = a^{\lfloor i / B \rfloor B}\times a^{i \bmod B}\) 回答询问即可。

时间复杂度:预处理 \(\mathcal{O}(\sqrt{n})\),查询 \(\mathcal{O}(1)\)

\(k\) 进制快速幂:利用 \(a^i = \left( a^k \right)^{\lfloor i / k \rfloor} \times a^{i \bmod k}\) 递归处理询问(右侧预处理,左侧递归)。

时间复杂度:预处理 \(\mathcal{O}(k \log_k n)\),查询 \(\mathcal{O}(\log_k n)\)

0x12 光速幂.cpp

// 光速幂
struct flashPower {
    int b;
    std::vector<int> w1, w2;

    flashPower() {}
    flashPower(int a, int n) {
        init(a, n);
    }

    void init(int a, int n) {
        b = sqrt(n) + 1;
        w1.resize(b + 1), w2.resize(b + 1);

        w1[0] = 1;
        for (int i = 1; i <= b; i ++) {
            w1[i] = 1ll * w1[i - 1] * a % mod;
        }

        w2[0] = 1;
        for (int i = 1; i <= b; i ++) {
            w2[i] = 1ll * w2[i - 1] * w1[b] % mod;
        }
    }

    int pow(int n) {
        return 1ll * w2[n / b] * w1[n % b] % mod;
    }
} fp;
posted @ 2022-12-19 10:24  Calculatelove  阅读(476)  评论(0)    收藏  举报