├── .gitignore ├── LICENSE ├── README.md ├── debug.h ├── BigInt.cpp ├── ExConclusion.md ├── Geometry.md ├── Others.md └── Graph.md /.gitignore: -------------------------------------------------------------------------------- 1 | /Collect.cpp 2 | /TemplateExtra.md 3 | /*.exe 4 | /.vscode 5 | /Axiomofchoice.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Axiomofchoice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACM-Axiomofchoice 2 | 3 | ## 简介 4 | 5 | 这是一个 ACM-XCPC 竞赛的技能树、代码仓库,由 HDU 吾有一數名之曰誒 (int a)、吾有一數名之曰嗶 (int b) 队员 Axiomofchoice 维护。 6 | 7 | ## 导航 8 | 9 | | 文件名 | 介绍 | 10 | | :---------------------------------------------------------------------------------------------------------: | :--------------------------------: | 11 | | [**Math.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Math.md) | 数学 | 12 | | [**Graph.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Graph.md) | 图论 | 13 | | [**Geometry.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Geometry.md) | 计算几何 | 14 | | [**Datastructure.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Datastructure.md) | 数据结构 | 15 | | [**Others.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Others.md) | 搜索、动态规划、字符串、编程技巧等 | 16 | | [**Conclusion.md**](https://github.com/axiomofchoice-hjt/ACM-axiomofchoice/blob/master/Conclusion.md) | 结论 | 17 | 18 | ## 代码风格 19 | 20 | - 之前:OI 风格(随便起的名),非必要不用空格、到处压行。 21 | - 现在:Google Style,4 缩进且有压行。 22 | - 模板里两种风格共存。 23 | 24 | 代码中的预定义: 25 | 26 | - 循环宏,`repeat (i, a, b)` 表示 `i` 从 `a` 循环到 `b - 1`,`repeat_back (i, a, b)` 表示 `i` 从 `b - 1` 反着循环到 `a`。 27 | 28 | ```cpp 29 | #define repeat(i, a, b) for (int i = (a), _ = (b); i < _; i++) 30 | #define repeat_back(i, a, b) for (int i = (b) - 1, _ = (a); i >= _; i--) 31 | ``` 32 | 33 | - 宏 `fi` 表示 `first`,`se` 表示 `second`。 34 | - 类型 `ll` 表示 `long long`,`lf` 表示 `double`,`pii` 表示 `pair`。 35 | - `rnd()` 会生成一个 64 位无符号整数范围内的随机数。 36 | - 宏 `mst(a, x)` 表示 `memset(a, x, sizeof(a))`。 37 | - 以前图方便用 `v << e` 表示 `v.push_back(e)`,正在逐渐减少这种写法。 38 | -------------------------------------------------------------------------------- /debug.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace std { 17 | // template 18 | // using to_void = void; 19 | 20 | // template 21 | // struct is_container : false_type {}; 22 | 23 | // template 24 | // struct is_container> : std::true_type {}; 25 | 26 | inline string toString(const bool &v) { return v ? "true" : "false"; } 27 | inline string toString(const char &v) { return (string) "'" + v + "'"; } 28 | inline string toString(const long double &v) { return to_string(double(v)); } 29 | inline string toString(const string &v) { return "\"" + v + "\""; } 30 | inline string toString(const char *const &v) { 31 | return (string) "\"" + v + "\""; 32 | } 33 | inline string toString(__uint128_t value) { 34 | if (value == 0) { 35 | return "0"; 36 | } 37 | string res; 38 | while (value != 0) { 39 | res += char(value % 10 + '0'); 40 | value /= 10; 41 | } 42 | reverse(res.begin(), res.end()); 43 | return res; 44 | } 45 | 46 | inline string toString(__int128_t value) { 47 | if (value < 0) { 48 | return '-' + toString((__uint128_t)-value); 49 | } else { 50 | return toString((__uint128_t)value); 51 | } 52 | } 53 | 54 | template 55 | string toString(T value, decltype(to_string(T())) * = nullptr) { 56 | return to_string(value); 57 | } 58 | 59 | template 60 | string toString(const T &value, decltype(begin(T())) * = nullptr) { 61 | string ans = "{"; 62 | bool flag = 0; 63 | for (const auto &i : value) { 64 | if (flag) { 65 | ans += ", "; 66 | } else { 67 | flag = 1; 68 | } 69 | ans += toString(i); 70 | } 71 | ans += "}"; 72 | return ans; 73 | } 74 | 75 | // pair 76 | template 77 | string toString(const pair &v) { 78 | return "(" + toString(v.first) + ", " + toString(v.second) + ")"; 79 | } 80 | // tuple 81 | template 82 | struct __TuplePrinter { 83 | static string __to_string(const Tuple &t) { 84 | return __TuplePrinter::__to_string(t) + ", " + 85 | toString(get(t)); 86 | } 87 | }; 88 | template 89 | struct __TuplePrinter { 90 | static string __to_string(const Tuple &t) { return toString(get<0>(t)); } 91 | }; 92 | template 93 | string toString(const std::tuple &t) { 94 | return "(" + __TuplePrinter::__to_string(t) + 95 | ")"; 96 | } 97 | // bitset 98 | template 99 | string toString(const bitset &container) { 100 | return "<" + container.to_string() + ">"; 101 | } 102 | // log 103 | inline void log_rest() { cerr << endl; } 104 | template 105 | void log_rest(const T &a, Args... x) { 106 | cerr << ", " << toString(a); 107 | log_rest(x...); 108 | } 109 | template 110 | void log_first(const T &a, Args... x) { 111 | cerr << toString(a); 112 | log_rest(x...); 113 | } 114 | template 115 | T log_first(const T &a) { 116 | cerr << toString(a) << endl; 117 | return a; 118 | } 119 | #define qwq [] { std::cerr << "qwq" << endl; }() 120 | // #define log(x) [&] { cerr << #x ": " << x << endl; return x; } () 121 | #define log(...) \ 122 | [&] { \ 123 | std::cerr << #__VA_ARGS__ ": "; \ 124 | return std::log_first(__VA_ARGS__); \ 125 | }() 126 | template 127 | void __logarr(const T &a, int n) { 128 | for (int i = 0; i < n; i++) { 129 | if (i != 0) cerr << ", "; 130 | cerr << toString(a[i]); 131 | } 132 | } 133 | #define logarr(a, n) \ 134 | [&] { \ 135 | std::cerr << #a ": ["; \ 136 | __logarr(a, n); \ 137 | std::cerr << "]" << std::endl; \ 138 | }() 139 | template 140 | void __logmat(const T &a, int n, int m) { 141 | for (int i = 0; i < n; i++) { 142 | cerr << " | "; 143 | __logarr(a[i], m); 144 | cerr << " |" << endl; 145 | } 146 | } 147 | #define logmat(a, n, m) \ 148 | [&] { \ 149 | std::cerr << #a ": mat(" << std::endl; \ 150 | __logmat(a, n, m); \ 151 | cerr << ")" << endl; \ 152 | }() 153 | #define pause \ 154 | [] { \ 155 | std::cerr << "pause at line " << __LINE__; \ 156 | getchar(); \ 157 | }() 158 | } // namespace std 159 | -------------------------------------------------------------------------------- /BigInt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | /** 5 | * @brief 二进制大数类 6 | * 支持与十进制字符串的互相转换、加法、减法、乘法。 7 | * (useless,需要普通的高精度板子转到 Math.md > 数学杂项 > struct of 高精度) 8 | * 9 | */ 10 | struct big { 11 | using u32 = unsigned; 12 | using ll = long long; 13 | using u64 = uint64_t; 14 | using vtr = vector; 15 | /// string 转换为 uint 16 | static u32 parseInt(const string &s) { 17 | u32 result = 0; 18 | for (char ch : s) result = result * 10 + ch - '0'; 19 | return result; 20 | } 21 | /// 高精度乘单精度 22 | static void mulInplace(vtr &x, u32 y) { 23 | u64 carry = 0; 24 | for (u32 &i : x) { 25 | carry = (u64)y * i + carry; 26 | i = carry; 27 | carry >>= 32; 28 | } 29 | adjust(x, carry); 30 | } 31 | /// 高精度加单精度 32 | static void addInplace(vtr &x, u32 y) { 33 | u64 carry = y; 34 | for (u32 &i : x) { 35 | carry += i; 36 | i = carry; 37 | carry >>= 32; 38 | } 39 | adjust(x, carry); 40 | } 41 | /// 若 carry 非 0,最高位进位 carry;否则去除前导 0 42 | static void adjust(vtr &x, u32 carry = 0) { 43 | if (carry) 44 | x.push_back(carry); 45 | else 46 | while (x.size() && x.back() == 0) x.pop_back(); 47 | } 48 | /// 高精度整除单精度,返回余数 49 | static u32 divModInplace(vtr &x, u32 m) { 50 | u64 remain = 0; 51 | for (int i = x.size() - 1; i >= 0; i--) { 52 | remain = remain << 32 | x[i]; 53 | x[i] = remain / m; 54 | remain %= m; 55 | } 56 | adjust(x); 57 | return remain; 58 | } 59 | /// 高精度加减高精度,sign 为 -1 表示减法,且必须满足 x > y 60 | static void addInplace(vtr &x, vtr y, int sign = 1) { 61 | if (sign == 1 && x.size() < y.size()) swap(x, y); 62 | ll carry = 0; 63 | for (u32 i = 0; i < y.size(); i++) { 64 | carry += x[i] + (ll)sign * y[i]; 65 | x[i] = carry; 66 | carry >>= 32; 67 | } 68 | for (u32 i = y.size(); i < x.size(); i++) { 69 | carry += x[i]; 70 | x[i] = carry; 71 | carry >>= 32; 72 | } 73 | adjust(x, carry); 74 | } 75 | /// 单精度比较 76 | static int compare(u32 x, u32 y) { return (x == y ? 0 : x > y ? 1 : -1); } 77 | /// 高精度比较 78 | static int compare(const vtr &x, const vtr &y) { 79 | if (x.size() != y.size()) return compare(x.size(), y.size()); 80 | if (x.size() == 0) return 0; 81 | for (u32 i = x.size() - 1; i != 0; i--) 82 | if (x[i] != y[i]) return compare(x[i], y[i]); 83 | return compare(x[0], y[0]); 84 | } 85 | /// 高精度乘法 Grade-School Algorithm 86 | static vtr mul(const vtr &x, const vtr &y) { 87 | vtr z(x.size() + y.size()); 88 | for (u32 i = 0; i < x.size(); i++) { 89 | u64 carry = 0; 90 | for (u32 j = 0; j < y.size(); j++) { 91 | carry += (u64)x[i] * y[j] + z[i + j]; 92 | z[i + j] = carry; 93 | carry >>= 32; 94 | } 95 | z[i + y.size()] = carry; 96 | } 97 | adjust(z); 98 | return z; 99 | } 100 | /// 左移 32 * nInts 位 101 | static void shiftLeft32Inplace(vtr &mag, u32 nInts) { 102 | mag.resize(mag.size() + nInts, 0); 103 | move_backward(mag.begin(), mag.end() - nInts, mag.end()); 104 | fill(mag.begin(), mag.begin() + nInts, 0); 105 | } 106 | /// 高精度乘法 Karatsuba Algorithm 107 | static vtr Karatsuba(const vtr &x, const vtr &y) { 108 | if (x.size() * y.size() <= 512) { 109 | return mul(x, y); 110 | } 111 | u64 half = (max(x.size(), y.size()) + 1) / 2; 112 | vtr xl(x.begin(), x.begin() + min(half, x.size())); 113 | vtr xh(x.begin() + min(half, x.size()), x.end()); 114 | vtr yl(y.begin(), y.begin() + min(half, y.size())); 115 | vtr yh(y.begin() + min(half, y.size()), y.end()); 116 | vtr p1 = Karatsuba(xh, yh); 117 | vtr p2 = Karatsuba(xl, yl); 118 | addInplace(xh, xl); 119 | addInplace(yh, yl); 120 | vtr p3 = Karatsuba(xh, yh); // 接下来计算答案 p1 = p1 * 2^(64h) + (p3 - 121 | // p1 - p2) * 2^(32h) + p2 122 | addInplace(p3, p1, -1); 123 | addInplace(p3, p2, -1); 124 | shiftLeft32Inplace(p1, half); 125 | addInplace(p1, p3); 126 | shiftLeft32Inplace(p1, half); 127 | addInplace(p1, p2); 128 | return p1; 129 | } 130 | 131 | /// 符号,1 是正数,-1 是负数,0 是等于 0 132 | int sign; 133 | /// 存放绝对值 134 | vtr mag; 135 | big() { sign = 0; } 136 | /// 用 ll 构造 137 | big(ll val) { 138 | sign = val > 0 ? 1 : val < 0 ? -1 : 0; 139 | if (sign == -1) val = -val; 140 | mag = {u32(val), u32((u64)val >> 32)}; 141 | adjust(mag); 142 | } 143 | /// 用 string 构造 144 | explicit big(string val) { 145 | const u32 len = val.size(); 146 | sign = (val[0] == '-' ? -1 : 1); 147 | u32 cursor = (val[0] == '-'); 148 | u32 groupLen = (len - cursor - 1) % 9 + 1; 149 | while (cursor < len) { 150 | string group = val.substr(cursor, groupLen); 151 | cursor += groupLen; 152 | mulInplace(mag, 1000000000); 153 | addInplace(mag, parseInt(group)); 154 | groupLen = 9; 155 | } 156 | if (mag.size() == 0) sign = 0; 157 | } 158 | /// 转换为 string 159 | string toString() const { 160 | if (sign == 0) return "0"; 161 | string result; 162 | vtr t = mag; 163 | while (t.size()) { 164 | string k = to_string(divModInplace(t, 1000000000)); 165 | if (t.size()) k = string(9 - k.size(), '0') + k; 166 | reverse(k.begin(), k.end()); 167 | result += k; 168 | } 169 | if (sign == -1) result += '-'; 170 | reverse(result.begin(), result.end()); 171 | return result; 172 | } 173 | /// 加法 174 | friend big operator+(big a, big b) { 175 | if (a.sign == 0) return b; 176 | if (b.sign == 0) return a; 177 | if (a.sign == b.sign) return addInplace(a.mag, b.mag), a; 178 | int c = compare(a.mag, b.mag); 179 | if (c == 0) return big(); 180 | if (c > 0) 181 | return addInplace(a.mag, b.mag, -1), a; 182 | else 183 | return addInplace(b.mag, a.mag, -1), b; 184 | } 185 | /// 减法 186 | friend big operator-(big a, big b) { 187 | b.sign = -b.sign; 188 | return a + b; 189 | } 190 | /// 乘法 191 | friend big operator*(const big &a, const big &b) { 192 | big z; 193 | z.sign = a.sign * b.sign; 194 | z.mag = Karatsuba(a.mag, b.mag); 195 | return z; 196 | } 197 | /// 小于 198 | bool operator<(const big &b) const { 199 | if (sign != b.sign) return sign < b.sign; 200 | return (compare(mag, b.mag) == -1) ^ (sign == -1); 201 | } 202 | /// 等于 203 | bool operator==(const big &b) const { 204 | return sign == b.sign && mag == b.mag; 205 | } 206 | }; 207 | 208 | signed main() { 209 | cout << (big("1111111") * big("1111111")).toString() << endl; 210 | return 0; 211 | } -------------------------------------------------------------------------------- /ExConclusion.md: -------------------------------------------------------------------------------- 1 | # ExConclusion 2 | 3 | - [1. 计算几何](#1-计算几何) 4 | - [1.1. 到给定点距离之和最小的直线](#11-到给定点距离之和最小的直线) 5 | - [1.2. 点集同构](#12-点集同构) 6 | - [1.3. 多边形构造](#13-多边形构造) 7 | - [2. 数论](#2-数论) 8 | - [2.1. 分母最小的分数](#21-分母最小的分数) 9 | - [3. 组合数学](#3-组合数学) 10 | - [3.1. 小众组合数学函数](#31-小众组合数学函数) 11 | - [4. 博弈论](#4-博弈论) 12 | - [4.1. 高维组合游戏 using Nim 积](#41-高维组合游戏-using-nim-积) 13 | - [4.2. 不平等博弈 using 超现实数](#42-不平等博弈-using-超现实数) 14 | - [4.3. 其他博弈结论](#43-其他博弈结论) 15 | - [5. 图论](#5-图论) 16 | - [5.1. 广义串并联图](#51-广义串并联图) 17 | - [5.2. 最小 k 度限制生成树 次小生成树](#52-最小-k-度限制生成树-次小生成树) 18 | - [5.3. 美术馆定理 using 多边形三角剖分](#53-美术馆定理-using-多边形三角剖分) 19 | - [5.4. 边匹配](#54-边匹配) 20 | - [5.5. 平面图 5-染色](#55-平面图-5-染色) 21 | - [5.6. 网络流](#56-网络流) 22 | - [5.7. 完全图欧拉回路](#57-完全图欧拉回路) 23 | - [6. 其他](#6-其他) 24 | - [6.1. 矩形里的小球](#61-矩形里的小球) 25 | - [6.2. 整数分解为 2 的幂的方案数](#62-整数分解为-2-的幂的方案数) 26 | - [6.3. 矩形孔明棋](#63-矩形孔明棋) 27 | - [6.4. 矩形匹配 using 根号算法](#64-矩形匹配-using-根号算法) 28 | - [6.5. 排序网络](#65-排序网络) 29 | 30 | ## 1. 计算几何 31 | 32 | ### 1.1. 到给定点距离之和最小的直线 33 | 34 | - shrink 一下让点集没有三点共线 35 | - 最优解一定是两点连线且把剩下点基本平分,因此可以绕直线上的某个点旋转直到碰到另外一点,然后绕新点旋转,重复上述操作,$O(n^2)$ 36 | 37 | 参考:CTSC 04 金恺 38 | 39 | ### 1.2. 点集同构 40 | 41 | - 给两个点集,问能否通过平移、旋转、翻转、缩放操作后重合 42 | - 求出质心,以质心到最远点的距离缩放,然后极角排序(第二关键字为距离),将二元组(极角差分,距离)列出来为 P, Q,求出 $PP$ 中是否有 Q 的出现即可 43 | - 翻转其中一个点集后再做一遍。特判质心位置处有点的情况 44 | 45 | ### 1.3. 多边形构造 46 | 47 | - 给 n 个数,构造凸 n 边形使得边具有给定长度 48 | - 若两倍最长边小于等于周长,那么一定可以构造圆内接 n 边形,对圆的半径二分即可 49 | - 判断考虑圆心是否在多边形内的情况,即对半径为最长边的二分之一判断 50 | 51 | ```cpp 52 | const lf eps=1e-10; 53 | int a[N],n; lf th[N]; 54 | bool work(lf r,bool state){ // state=1 means outside 55 | th[0]=asin(a[0]/(2*r))*2; 56 | if(state)th[0]=pi*2-th[0]; 57 | repeat(i,1,n) 58 | th[i]=th[i-1]+asin(a[i]/(2*r))*2; 59 | return (th[n-1]=sum){ 66 | puts("NO SOLUTION"); 67 | return; 68 | } 69 | lf l=a[0]/2.0,r=1e7; 70 | bool state=work(l,0); 71 | while((r-l)/r>eps){ 72 | lf mid=(l+r)/2; 73 | if(work(mid,state))r=mid; 74 | else l=mid; 75 | } 76 | repeat(i,0,n){ 77 | printf("%.12f %.12f\n",cos(th[i])*r,sin(th[i])*r); 78 | } 79 | } 80 | ``` 81 | 82 | ## 2. 数论 83 | 84 | ### 2.1. 分母最小的分数 85 | 86 | - 求 $\dfrac a b<\dfrac p q<\dfrac c d$ 的分母最小的 $\dfrac p q$ 87 | - 若存在整数则直接解决。否则有 $0\le \dfrac{a \bmod b}{b}<\dfrac{p'}{q'}<\dfrac{c\bmod d}{d}<1$,$\dfrac{p'}{q'}+a/b=\dfrac p q$,再取倒数 $\dfrac{d}{c\bmod d}<\dfrac{q'}{p'}<\dfrac{b}{a\bmod b}$,如此反复 88 | 89 | ## 3. 组合数学 90 | 91 | ### 3.1. 小众组合数学函数 92 | 93 | - 超级卡特兰数 $S_n$ 94 | - (0, 0) 走到 (n, n) 方案数,只能往右、上、右上走,且满足 $y\le x$ 95 | - $S_{0..10}=1, 2, 6, 22, 90, 394, 1806, 8558, 41586, 206098, 1037718$ 96 | - $\displaystyle S_n=S_{n-1}+\sum_{k=0}^{n-1}S_kS_{n-1-k}$ 97 | - $F_0=S_0,2F_i=S_i,F_n=\dfrac{(6n-3)F_{n-1}-(n-2)F_{n-2}}{n+1}$ 98 | - 通项公式 $\displaystyle S_n=\dfrac{1}{n}\sum_{k=1}^n2^kC_n^kC_n^{k-1},n\ge1$ 99 | - 若 $n\times n$ 矩阵 $A_{i,j}=S_{i+j-1}$,则 $\det A=2^{\tfrac{n(n+1)}{2}}$ 100 | - $S_n$ 为在 $n\times n$ 的矩形中选定 n 个点和 n 条水平/竖直的线段,满足每条线段恰好经过一个点且每个点恰好只被一条线段经过且线段直接不出现十字交叉,这 n 条线段把矩形划分成 $n+1$ 个小矩形的方案数 101 | 102 | *** 103 | 104 | - A001006 Motzkin 数 $M_n$ 105 | - 1, 1, 2, 4, 9, 21, 51, 127, 323, 835, 2188, 5798, 15511, 41835, 113634, 310572, 853467 106 | - 表示在圆上 n 个点连接任意个不相交弦的方案数 107 | - 也是 (0, 0) 走到 (n, 0) ,只能右/右上/右下走,不能走到 x 轴下方的方案数 108 | - $M_0=1,M_n=\tfrac{2n+1}{n+2}M_{n-1}+\tfrac{3n-3}{n+2}M_{n-2}$ 109 | 110 | *** 111 | 112 | - Eulerian 数 $\left\langle n\atop m\right\rangle$ 113 | - 表示 $1\ldots n$ 的排列,有 m 个数比它前一个数大的方案数 114 | - $\left\langle 1\atop 0\right\rangle=1,\left\langle n\atop m\right\rangle=(n-m)\left\langle n-1\atop m-1\right\rangle+(m+1)\left\langle n-1\atop m\right\rangle$ 115 | - $\sum_{m=0}^{n-1}\left\langle n\atop m\right\rangle=n!$ 116 | 117 | *** 118 | 119 | - Narayana 数 $N(n,k)$ 120 | - $N(n,k)=\dfrac 1 n\dbinom{n 121 | }{k}\dbinom{n}{k-1}$ 122 | - $C_n=\sum\limits_{i=1}^nN(n,i)$(卡特兰数) 123 | - 表示 n 对匹配括号组成的字符串中有 k 个 `()` 子串的方案数 124 | - 表示 (0, 0) 走到 $(2n,0)$,只能右上/左下,有 k 个波峰的方案数 125 | 126 | *** 127 | 128 | - Delannoy 数 $D(m,n)$ 129 | - 表示 (0, 0) 走到 (m, n),只能右/上/右上的方案数 130 | - 递推公式即简单 dp 131 | - $D(m,n)=\sum\limits_{k=0}^{\min(m,n)}{m+n-k\choose m}{m\choose k}$ 132 | - $D(m,n)=\sum\limits_{k=0}^{\min(m,n)}{m\choose k}{n\choose k}2^k$ 133 | 134 | *** 135 | 136 | - A001003 Hipparchus 数 / 小 Schroeder 数 $S(n)$ 137 | - 1, 1, 3, 11, 45, 197, 903, 4279, 20793, 103049, 518859, 2646723, 13648869, 71039373 138 | - 表示 (0, 0) 走到 (n, n),只能右/上/右上,不能沿 $y=x$ 走,且只能在 $y\le x$ 区域走的方案数 139 | - $S(0)=S(1)=1,S(n)=\frac{(6n-3)S(n-1)-(n-2)S(n-2)}{n+1}$ `s[i]=((6*i-3)*s[i-1]-(i-2)*s[i-2])/(i+1)` 140 | - 表示 $n+2$ 边形的多边形剖分数 141 | 142 | *** 143 | 144 | - A000670 Fubini 数 $a(n)$ 145 | - 1, 1, 3, 13, 75, 541, 4683, 47293, 545835, 7087261, 102247563, 1622632573, 28091567595 146 | - 表示 n 个元素组成偏序集的个数 147 | - $a(0)=1,a(n)=\sum_{k=1}^{n}\dbinom n k a(n-k)$ 148 | 149 | *** 150 | 151 | - A000111 Euler 数 $E(n)$ 152 | - 1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521, 353792, 2702765, 22368256, 199360981 153 | - 其指数型生成函数为 $\dfrac{1}{\cos x}+\tan x$,前者提供偶数项 (A000364),后者提供奇数项 154 | - 表示满足 $x_1>x_2x_4<\ldots x_n$ 的排列的方案数 155 | 156 | ```cpp 157 | vi calc(int n){ 158 | n=polyn(n); 159 | return eachfac(conv( 160 | inv(cos(vi({0,1}),n),n), // 1/cos(x) 161 | sin(vi({0,1}),n), // sin(x) 162 | n,fxy((x*y+x)%mod) 163 | ),n); // 1/cos(x)+tan(x) 164 | } 165 | ``` 166 | 167 | *** 168 | 169 | - [A014430](http://oeis.org/A014430) 减一的杨辉矩阵(但是下标的含义不太一样) 170 | 171 | $$\left[\begin{array}{c}1 & 2 & 3 & 4 & 5 \newline 2 & 5 & 9 & 14 & 20 \newline 3 & 9 & 19 & 34 & 55 \newline 4 & 14 & 34 & 69 &125 \newline 5 & 20 & 55 & 125 & 251 \newline \end{array}\right]$$ 172 | 173 | - 定义:$T(n,m)=\dbinom{n+m+2}{n+1}-1$ 174 | - 递推式:$T(n,k)=T(n-1,k)+T(n,k-1)+1, T(0,0)=1$ 175 | - 它是杨辉矩阵前缀和:$\displaystyle T(n,m)=\sum_{i=0}^n\sum_{j=0}^m\dbinom{i+j}{i}$ 176 | 177 | *** 178 | 179 | - 拉氏数 Lah 180 | 181 | $$\begin{aligned}L(n,k)&=(-1)^n\dfrac{n!}{k!}\dbinom{n-1}{k-1}\newline L'(n,k)&=\dfrac{n!}{k!}\dbinom{n-1}{k-1}\end{aligned}$$ 182 | 183 | - 拉氏反演:(存疑) 184 | 185 | $$\begin{aligned}a_n&=\sum_{k=0}^{n}L(n,k)b_k\newline b_n&=\sum_{k=0}^{n}L(n,k)a_k\end{aligned}$$ 186 | 187 | *** 188 | 189 | - 高斯系数 190 | 191 | $$\dbinom{n}{k}_q=\dfrac{(q^n-1)(q^{n-1}-1)\ldots(q^{n-(m-1)}-1)}{(q^m-1)(q^{m-1}-1)\ldots(q-1)}$$ 192 | 193 | - 高斯系数反演:(存疑) 194 | 195 | $$\begin{aligned}a_n&=\sum_{k=0}^{n}\dbinom{n}{k}_qb_k\newline b_n&=\sum_{k=0}^{n}(-1)^{n-k}q^{\tbinom{n-k}{2}}\dbinom{n}{k}_qa_k\end{aligned}$$ 196 | 197 | *** 198 | 199 | [A002137](http://oeis.org/A002137) $n\times n$ 对称矩阵的个数,对角为 0,行和为 2,$a_n = (n-1)(a_{n-1}+a_{n-2}) - \dfrac{1}{2}(n-1)(n-2)a_{n-3}$ 200 | 201 | ## 4. 博弈论 202 | 203 | ### 4.1. 高维组合游戏 using Nim 积 204 | 205 | - Nim 和与Nim 积的关系类似加法与乘法 206 | - Tartan 定理:对于一个高维的游戏(多个维度的笛卡尔积),玩家的操作也是笛卡尔积的形式,那么对每一维度单独计算 SG 值,最终的 SG 值为它们的 Nim 积 207 | - 比如,在 $n\times m$ 硬币中翻转 4 个硬币,4 个硬币构成一个矩形,这个矩形是每一维度(翻转两个硬币)的笛卡尔积 208 | - $O(\log^2 n)$ 209 | 210 | ```cpp 211 | struct Nim{ 212 | ll rec[256][256]; 213 | ll f(ll x,ll y,int len=32) { 214 | if(x==0 || y==0) return 0; 215 | if(x==1 || y==1) return x*y; 216 | if(len<=4 && rec[x][y]) return rec[x][y]; 217 | ll xa=x>>len,xb=x^(xa<>len,yb=y^(ya<>1),b=f(xa^xb,ya^yb,len>>1),c=f(xa,ya,len>>1),d=f(c,1ll<<(len-1),len>>1); 219 | ll ans=((b^a)<0$ 则左玩家必胜,$<0$ 则右玩家必胜,$=0$ 则后手必胜 234 | - 一个博弈局面,L 为左玩家操作一次后的博弈局面的最大值,R 为右玩家操作一次后的博弈局面的最小值,那么该博弈局面的值 $G=\dfrac A {2^B},L 0$ 则 W 必胜;若 $= 0$ 则后手必胜;若 $< 0$ 则 B 必胜 242 | 243 | ```cpp 244 | ll calc(char s[]){ 245 | int n=strlen(s); 246 | ll ans=0,k=1LL<<50; int i; 247 | for(i=0;i>=1,ans+=(s[i]=='W'?k:-k); 251 | return ans; 252 | } 253 | ``` 254 | 255 | ```cpp 256 | int ans[N]; 257 | void calc(char s[]){ 258 | int n=strlen(s); 259 | int p=0; while(s[p]==s[0])p++; 260 | ans[0]+=(s[0]=='W'?p:-p); 261 | repeat(i,p,n)ans[i-p+1]+=(s[i]=='W'?1:-1); 262 | } 263 | void adjust(){ 264 | repeat_back(i,1,N) 265 | ans[i-1]+=ans[i]/2,ans[i]%=2; 266 | } 267 | ``` 268 | 269 | *** 270 | 271 | - Blue-Red Hackenbush tree 272 | - 若干棵树,点权为 W 或 B,player-W 只能删 W,player-B 只能删 B,每次删点后与根不相连部分也移除 273 | - 对于 W 点,先求所有儿子的值之和 x。如果 $x \ge 0$,那么直接加一即可。否则 x 变为 x 的小数部分加一,乘以 $2^{-\lfloor|x|\rfloor}$ 274 | 275 | *** 276 | 277 | - Alice's Game 278 | - $x\times y$ 方格,如果 $x>1$ Alice可以水平切,如果 $y>1$ Bob可以垂直切,超现实数计算如下 279 | 280 | ```cpp 281 | ll calc(int x,int y){ // get surreal number 282 | while(x>1 && y>1)x>>=1,y>>=1; 283 | return x-y; 284 | } 285 | ``` 286 | 287 | ### 4.3. 其他博弈结论 288 | 289 | 欧几里得的游戏 290 | 291 | - 两个数 a, b,每次对一个数删去另一个数的整数倍,出现 0 则失败 292 | - $a\ge 2b$ 则先手必胜,否则递归处理 293 | 294 | *** 295 | 296 | 无向点地理问题 / Undirected vertex geography problem 297 | 298 | - 二分图上移动棋子,不能经过重复点 299 | - 先手必败当且仅当存在一个不包含起点的最大匹配 300 | 301 | *** 302 | 303 | 博弈论 Shannon 开关游戏 304 | 305 | - refer to CTSC 07 刘雨辰 306 | - 给无向图,玩家P可以在没有标记的边上标+号,玩家N可以在删除一条没有标记的边,轮流操作直到不能操作。若最终的图连通则玩家P获胜 307 | - 玩家P后手必胜当且仅当存在两棵边独立的生成树 308 | - 若玩家P获胜条件改为顶点 u, v 连通,则玩家P后手必胜当且仅当原图的一个包含 u, v 的导出子图存在两棵边独立的生成树 309 | 310 | *** 311 | 312 | - 1 到 n,每次拿一个数或差值为 1 的两个数 313 | - 先手必胜,第一步拿最中间的 1 或 2 个数,之后对称操作 314 | - $n\times m$ 棋盘上两个棋子,每次双方可以操控自己的棋子移动到同一行/列的位置,不能经过对方棋子所在行/列 315 | - 后手必胜当且仅当两个棋子的横坐标之差等于纵坐标之差 316 | - 2 个数字,每次把一个数字减少,最小 1,但是不能出现重复数字 317 | - $\text{SG}(a,b)=((a-1)\oplus(b-1))-1$ 318 | - 3 个数字,每次把一个数字减少,最小 1,但是不能出现重复数字 319 | - 后手必胜当且仅当 $a\oplus b\oplus c=0$ 320 | - Octal Game 表明一些 Nim 游戏的 SG 值存在周期性(允许一些例外) 321 | 322 | *** 323 | 324 | 奇偶博弈(名字乱取的) 325 | 326 | - n 堆石子,玩家可选若干堆石子,每堆取一个,不能不取。 327 | - 先手必胜当且仅当存在奇数个石子的堆。 328 | 329 | ## 5. 图论 330 | 331 | ### 5.1. 广义串并联图 332 | 333 | - 无 $K_4$ 子图的无向连通图称为广义串并联图。 334 | - 简单广义串并联图有 $E\le 2V$。 335 | - 广义串并联图可经过若干收缩操作变成一个顶点的图。 336 | - 删除度为 1 的点。 337 | - 删除重边并用权为两边较小值的边替换。 338 | - 删除度为 2 的点并用权为两边之和的边替换。(优先执行操作 2) 339 | - 收缩操作可建立串并联树。 340 | - 初始每个点 v 和边 e 都对应叶子 $T_v,T_e$。 341 | - 对于删度为 1 的点 x,对应边为 $e=\langle x,y\rangle$,新建点 $T_z$ 作为 $T_e,T_x,T_y$ 的父亲并将 y 对应节点设为 $T_z$。 342 | - 删除重边 a, b,新建点 $T_c$ 作为 $T_a,T_b$ 的父亲并对应原图新加的边 c。 343 | - 对于删度为 2 的点 x,对应边为 a, b,新建点 $T_c$ 作为 $T_x,T_a,T_b$ 的父亲并对应原图新加的边 c。 344 | - 非叶子节点 u 345 | - 咕了 346 | 347 | ### 5.2. 最小 k 度限制生成树 次小生成树 348 | 349 | 最小 k 度生成树 350 | 351 | - 即某个点 $v_0$ 度不大于 k 的最小生成树 352 | - 去掉 $v_0$ 跑一遍最小生成森林,然后从小到大访问 $v_0$ 的边 $(v_0,v)$ 考虑是否能加入边集 353 | - 如果 v 与 $v_0$ 不连通就直接加,否则判断路径 $v_0 - v$ 上的最大边是否大于 $(v_0,v)$,大于就将它替换为 $(v_0,v)$ 354 | - 令 $Best(v)$ 为路径 $v_0-v$ 的最大边,每次树的形态改变后更新 $Best$ 355 | - 可以证明最优性,$O(E\log E+kV)$(不知道可不可以数据结构维护) 356 | 357 | 次小生成树 358 | 359 | - 即所有生成树中第二小的生成树 360 | - 跑一遍Kruskal,然后对剩下的边依次询问这条边两个端点的路径最长边,更新答案。树上倍增优化,$O(E\log E)$ 361 | 362 | ### 5.3. 美术馆定理 using 多边形三角剖分 363 | 364 | - 多边形三角剖分:设 $A,B,C$ 为连续的三点,若所有其他顶点不在 $\triangle ABC$ 内,则将原多边形用线段 $AC$ 划分;否则必然存在 $\triangle ABC$ 内且离线段 $AC$ 最远的点 D,则将原多边形用线段 $BD$ 划分 365 | - 美术馆定理:对任意 n 边形美术馆,一定可以放置 $\lfloor \dfrac n 3\rfloor$ 个守卫(守卫具有 $360\degree$ 视角)来看守整个美术馆 366 | - 对美术馆进行三角剖分,并对所有顶点 3-染色,保证任意两条边有不同颜色(任意三角形的顶点有三种颜色)。对三种颜色的点集取最小的点集即可 367 | 368 | ### 5.4. 边匹配 369 | 370 | - 求无向图最大的相邻边二元组集合,两两二元组无公共边。 371 | - $O(n)$ 372 | 373 | ```cpp 374 | vector a[N]; // a[][].second: edge id 375 | bool vis[N],instk[N]; 376 | vector ans; // result, set of 377 | void push(int &x,int &y){ 378 | if(x!=-1 && y!=-1) 379 | ans.push_back({x,y}),x=y=-1; 380 | } 381 | int dfs(int x){ 382 | vis[x]=1; instk[x]=1; 383 | int r=-1; 384 | for(auto i:a[x]){ 385 | int p=i.fi,e=i.se; 386 | if(instk[p])continue; 387 | if(!vis[p]){ 388 | int t=dfs(p); 389 | push(e,t); 390 | } 391 | push(e,r); 392 | if(r==-1)r=e; 393 | } 394 | instk[x]=0; 395 | return r; 396 | } 397 | void solve(){ 398 | repeat(i,0,n)if(!vis[i])dfs(i); 399 | } 400 | ``` 401 | 402 | ### 5.5. 平面图 5-染色 403 | 404 | - 平面图欧拉定理: 405 | - $|V|-|E|+|F|=2$ 406 | - 给定连通简单平面图,若 $|V|≥3$,则 $|E|≤3|V|-6$ 407 | - 可知平面图的边数为 $O(V)$ 408 | - 补充:给定连通简单平面图,若 $|V|≥3$,则 $\exist v,deg(v)\le 5$ 409 | - 由 $\exist deg(v)\le 5$,找到度最小的点 u,递归地对剩下的图进行5-染色 410 | - 然后考虑 u 的颜色,如果 u 的邻居中 5 种颜色没有都出现,就直接染色,否则考虑顺时针的 5 个邻居 $v_1,v_2,v_3,v_4,v_5$,考虑两个子图,与 $v_1$ 或 $v_3$ 颜色相同的点集的导出子图 $G_1$,与 $v_2$ 或 $v_4$ 颜色相同的点集的导出子图 $G_2$,则 $v_1,v_3$ 在 $G_1$ 中不连通、$v_2,v_4$ 在 $G_2$ 中不连通两个命题必然有一个成立(否则出现两个相互嵌套的环,不构成平面图),假设 $v_1,v_3$ 在 $G_1$ 中不连通,那么将 $G_1$ 中 $v_1$ 的连通分量的所有顶点颜色取反($color[v_1]$ 与 $color[v_2]$ 互换),这样 u 的邻居变为 4 种颜色,将 u 染色为原来的 $color[v_1]$ 411 | - 如果顺时针的关系很难得到,就尝试两次($[v_1,v_2,v_3,v_4],[v_1,v_3,v_2,v_4]$) 412 | 413 | ### 5.6. 网络流 414 | 415 | - 多物网络流:k 个源汇点,$S_i$ 需要流 $f_i$ 单位流量至 $T_i$。多物网络流只能用线性规划解决。 416 | 417 | ### 5.7. 完全图欧拉回路 418 | 419 | - 可以得到任意连续 n − 2 个点都两两不重复的完全图欧拉回路(n 是奇数)。 420 | 421 | ```cpp 422 | vector euler(int n) { 423 | vector ans = {n - 1}; 424 | repeat (i, 0, n / 2) { 425 | int sgn = 1, ct = i; 426 | repeat (d, 1, n) { 427 | ans.push_back(ct); 428 | ct = (ct + sgn * d + n - 1) % (n - 1); 429 | sgn *= -1; 430 | } 431 | ans.push_back(n - 1); 432 | } 433 | return ans; 434 | } 435 | ``` 436 | 437 | ## 6. 其他 438 | 439 | ### 6.1. 矩形里的小球 440 | 441 | - $(n+1)\times (m+1)$ 矩形中,小球从左下顶点往上 $a(a=0..n)$ 格的位置向右上发射,在矩形边界处反弹,回到起点后停止。问有几个格子经过了奇数次(起点只记一次) 442 | - 情况1:撞到角上完全反弹,$\gcd(n,m)\mid a$(包含 $a=0$),答案为 2(角和起点) 443 | - 情况2:没有撞到角上,$\gcd(n,m)\nmid a$,答案为 $\dfrac{2(nm-n(m/g-1)-m(n/g-1))}{g}$(经过的格子数 $\tfrac {2nm}{g}$ 减去经过两次的格子数)(这种情况最多经过两次) 444 | 445 | 参考:CTSC 03 姜尚仆 446 | 447 | ### 6.2. 整数分解为 2 的幂的方案数 448 | 449 | - 即 A018819 $[1, 1, 2, 2, 4, 4, 6, 6, 10, 10, 14, 14, ...]$ 450 | - 递推式 $a_{2m+1} = a_{2m}, a_{2m} = a_{2m-1} + a_{m}$ 451 | - 用矩阵乘法可以加速,$O(\log^4 n)$ 452 | 453 | ```cpp 454 | void Solve(){ 455 | int q=read(); 456 | n=1; 457 | mat t(1); vector a={1}; 458 | while(q){ 459 | if(q&1)a=t*a; 460 | t=t*t; 461 | n++; 462 | copy_n(t[n-2],n,t[n-1]); 463 | t[n-1][n-1]=1; 464 | a.push_back(a.back()); 465 | q>>=1; 466 | } 467 | cout<k$,直接处理这一行的贡献后删除该行。处理方式为,先将第 i 行的列号处理为一个集合,统计第 j 行里出现在集合的点数 $=h(j)$,$\displaystyle\sum_j {h(j)\choose 2}$ 即为贡献。操作最多 $\dfrac n k$ 行,因此复杂度 $O(\dfrac{n^2}{k})$ 487 | - 剩下的点中每行点数 $\le k$,考虑对每行的点集两两匹配,暴力统计 $(x_1,y)(x_2,y)$ 中 y 的个数 $=h(x_1,x_2)$,$\displaystyle\sum_{x_1,x_2}{h(x_1,x_2)\choose 2}$ 即为贡献。操作最少 $\dfrac n k$ 行,因此复杂度 $O(nk)$ 488 | 489 | ### 6.5. 排序网络 490 | 491 | - $O(\log^2 n)$ 492 | 493 | ```text 494 | --X------X----X------X--------X----X---- 495 | --X------|-X--X------|-X------|-X--X---- 496 | ----X----|-X----X----|-|-X----X-|----X-- 497 | ----X----X------X----|-|-|-X----X----X-- 498 | --X------X----X------|-|-|-X--X----X---- 499 | --X------|-X--X------|-|-X----|-X--X---- 500 | ----X----|-X----X----|-X------X-|----X-- 501 | ----X----X------X----X----------X----X-- 502 | ``` 503 | -------------------------------------------------------------------------------- /Geometry.md: -------------------------------------------------------------------------------- 1 | # 计算几何 2 | 3 | - [1. struct of 向量](#1-struct-of-向量) 4 | - [2. struct of 直线](#2-struct-of-直线) 5 | - [3. struct of 圆](#3-struct-of-圆) 6 | - [4. 平面几何基本操作](#4-平面几何基本操作) 7 | - [4.1. 判断两条线段是否相交](#41-判断两条线段是否相交) 8 | - [4.2. 点是否在线段上](#42-点是否在线段上) 9 | - [4.3. 多边形面积](#43-多边形面积) 10 | - [4.4. 多边形的面积质心](#44-多边形的面积质心) 11 | - [4.5. 凸包切线](#45-凸包切线) 12 | - [4.6. 凸包与圆的面积交](#46-凸包与圆的面积交) 13 | - [5. 二维凸包](#5-二维凸包) 14 | - [5.1. \<补充\> 动态凸包](#51-补充-动态凸包) 15 | - [6. 旋转卡壳](#6-旋转卡壳) 16 | - [7. 最大空矩形 using 扫描法](#7-最大空矩形-using-扫描法) 17 | - [8. 平面最近点对 using 分治](#8-平面最近点对-using-分治) 18 | - [9. 最小圆覆盖 using 随机增量法](#9-最小圆覆盖-using-随机增量法) 19 | - [10. 半面交 using S\&I 算法](#10-半面交-using-si-算法) 20 | - [11. struct of 整点直线](#11-struct-of-整点直线) 21 | - [12. 整点向量线性基](#12-整点向量线性基) 22 | - [13. 曼哈顿最小生成树](#13-曼哈顿最小生成树) 23 | - [14. 圆的离散化](#14-圆的离散化) 24 | - [15. Delaunay 三角剖分](#15-delaunay-三角剖分) 25 | - [16. struct of 三维向量](#16-struct-of-三维向量) 26 | - [17. 三维凸包](#17-三维凸包) 27 | 28 | ## 1. struct of 向量 29 | 30 | - `rotate()` 返回逆时针旋转后的点,`left()` 返回朝左的单位向量 31 | - `trans()` 返回 p 沿 a, b 拉伸的结果,`arctrans()` 返回 p 在坐标系 `` 中的坐标 32 | - 常量式写法,不要另加变量,需要加变量就再搞个 struct 33 | - 直线类在半面交里,其中包含线段交点 34 | 35 | ```cpp 36 | struct vec { 37 | lf x, y; vec() {} vec(lf x, lf y) : x(x), y(y) {} 38 | vec operator-(const vec &b) { return vec(x - b.x, y - b.y); } 39 | vec operator+(const vec &b) { return vec(x + b.x, y + b.y); } 40 | vec operator*(lf k) { return vec(k * x, k * y); } 41 | lf len() { return hypot(x, y); } 42 | lf sqr() { return x * x + y * y; } 43 | vec trunc(lf k = 1) { return *this * (k / len()); } 44 | vec rotate(double th) { 45 | lf c = cos(th), s = sin(th); 46 | return vec(x * c - y * s, x * s + y * c); 47 | } 48 | vec left() { return vec(-y, x).trunc(); } 49 | lf theta() { return atan2(y, x); } 50 | friend lf cross(vec a, vec b) { return a.x * b.y - a.y * b.x; } 51 | friend lf cross(vec a, vec b, vec c) { return cross(a - c, b - c); } 52 | friend lf dot(vec a, vec b) {return a.x * b.x + a.y * b.y; } 53 | friend lf cos(vec a, vec b) { return dot(a, b) / sqrt(a.sqr() * b.sqr()); } 54 | friend vec trans(vec p, vec a, vec b) { 55 | swap(a.y, b.x); 56 | return vec(dot(a, p), dot(b, p)); 57 | } 58 | friend vec arctrans(vec p, vec a, vec b) { 59 | lf t = cross(a, b); 60 | return vec(-cross(b, p) / t, cross(a, p) / t); 61 | } 62 | void output() { printf("%.12f %.12f\n", x, y); } 63 | } a[N]; 64 | vec projection(vec v, vec a, vec b) { // v 在 line(a,b) 上的投影 65 | vec d = b - a; 66 | return a + d * (dot(v - a, d) / d.sqr()); 67 | } 68 | ``` 69 | 70 | - 整数向量 71 | 72 | ```cpp 73 | struct vec{ 74 | ll x, y; vec() {} vec(ll x, ll y): x(x), y(y) {} 75 | vec operator-(const vec &b) { return vec(x - b.x, y - b.y); } 76 | vec operator+(const vec &b) { return vec(x +b .x, y + b.y); } 77 | vec operator*(ll k) { return vec(k * x, k * y); } 78 | bool operator==(vec b) const { return x == b.x && y == b.y; } 79 | friend ll cross(vec a, vec b) { return a.x * b.y - a.y * b.x; } 80 | friend ll cross(vec a, vec b, vec c) { return cross(a - c, b - c); } 81 | friend ll dot(vec a, vec b) {return a.x * b.x + a.y * b.y; } 82 | ll sqr_dist(vec b) { return sqr(x - b.x) + sqr(y - b.y); } 83 | // ll sqr() { return x * x + y * y; } 84 | void output() { printf("%lld %lld\n", x, y); } 85 | } a[N]; 86 | ``` 87 | 88 | ## 2. struct of 直线 89 | 90 | ```cpp 91 | struct line{ 92 | vec p1,p2; lf th; 93 | line(){} 94 | line(vec p1,vec p2):p1(p1),p2(p2){ 95 | th=(p2-p1).theta(); 96 | } 97 | bool contain(vec v){ // 直线是否包含给定点 98 | return cross(v,p2,p1)<=eps; 99 | } 100 | vec PI(line b){ // 两条直线的交点 101 | lf t1=cross(p1,b.p2,b.p1); 102 | lf t2=cross(p2,b.p2,b.p1); 103 | return vec((t1*p2.x-t2*p1.x)/(t1-t2),(t1*p2.y-t2*p1.y)/(t1-t2)); 104 | } 105 | }; 106 | ``` 107 | 108 | ## 3. struct of 圆 109 | 110 | ```cpp 111 | struct cir{ 112 | vec v; lf r; 113 | void PI(vec a,vec b,vec &A,vec &B){ // 与直线 (a, b) 的交点 114 | vec H=projection(v,a,b); 115 | vec D=(a-b).trunc(sqrt(r*r-(v-H).sqr())); 116 | A=H+D; B=H-D; 117 | } 118 | void PI(cir b,vec &A,vec &B){ // 与圆 c 的交点 119 | vec d=b.v-v; 120 | lf dis=abs(b.r*b.r-r*r-d.sqr())/(2*d.len()); 121 | vec H=v+d.trunc(dis); 122 | vec D=d.left().trunc(sqrt(r*r-dis*dis)); 123 | A=H+D; B=H-D; 124 | } 125 | }; 126 | ``` 127 | 128 | ## 4. 平面几何基本操作 129 | 130 | ### 4.1. 判断两条线段是否相交 131 | 132 | - 快速排斥实验:判断线段所在矩形是否相交(用来减小常数,可省略) 133 | - 跨立实验:任一线段的两端点在另一线段的两侧 134 | 135 | ```cpp 136 | bool judge(vec a,vec b,vec c,vec d){ // 线段 ab 和线段 cd 137 | #define SJ(x) max(a.x,b.x) a[right].x) right = i; 191 | } 192 | vec v; v.x = read(), v.y = read(); // 读入凸包外一点 193 | int l = left, r = right; if (l > r) r += n; 194 | while (l < r) { // 上凸包三分极远点 195 | int x = (l + r) / 2, y = x + 1; 196 | if (a[x].dist(v) < a[y].dist(v)) l = x + 1; else r = y - 1; 197 | } 198 | int mx = l % n; 199 | l = right, r = left; if (l > r) r += n; 200 | while (l < r) { // 下凸包三分极远点 201 | int x = (l + r) / 2, y = x + 1; 202 | if (a[x].dist(v) < a[y].dist(v)) l = x + 1; else r = y - 1; 203 | } 204 | if (a[l % n].dist(v) > a[mx].dist(v)) mx = l % n; 205 | l = left, r = right; if (l > r) r += n; 206 | while (l < r) { // 上凸包三分最近点 207 | int x = (l + r) / 2, y = x + 1; 208 | if (a[x].dist(v) > a[y].dist(v)) l = x + 1; else r = y - 1; 209 | } 210 | int mn = l % n; 211 | l = right, r = left; if (l > r) r += n; 212 | while (l < r) { // 下凸包三分最近点 213 | int x = (l + r) / 2, y = x + 1; 214 | if (a[x].dist(v) > a[y].dist(v)) l = x + 1; else r = y - 1; 215 | } 216 | if (a[l % n].dist(v) < a[mn].dist(v)) mn = l % n; 217 | auto see = [](vec v, vec a, vec b) { return cross(b, v, a) < 0; }; 218 | l = mn, r = mx - 1; if (l > r) r += n; 219 | while (l <= r) { // 二分切线(其一) 220 | int mid = (l + r) / 2; 221 | if (see(v, a[mid], a[mid + 1])) l = mid + 1; 222 | else r = mid - 1; 223 | } 224 | R = (r + n) % n + 1; 225 | l = mx, r = mn - 1; if (l > r) r += n; 226 | while (l <= r) { // 二分切线(其二) 227 | int mid = (l + r) / 2; 228 | if (!see(v, a[mid], a[mid + 1])) l = mid + 1; 229 | else r = mid - 1; 230 | } 231 | L = (l + n) % n + 1; 232 | // L, R + 1 为切点,边 (L, L + 1) ... (R, R + 1) 可以被看见。 233 | ``` 234 | 235 | ### 4.6. 凸包与圆的面积交 236 | 237 | ```cpp 238 | const double PI=acos(-1); 239 | const double eps=1e-10; 240 | inline int sgn(double x){return fabs(x)circle_cross_line(point a,point b,point o,double r){ 268 | double dx=b.x-a.x,dy=b.y-a.y; 269 | double A=dx*dx+dy*dy; 270 | double B=2*dx*(a.x-o.x)+2*dy*(a.y-o.y); 271 | double C=(a.x-o.x)*(a.x-o.x)+(a.y-o.y)*(a.y-o.y)-r*r; 272 | double delta=B*B-4*A*C; 273 | vectorvi; 274 | if(sgn(delta)>=0){ 275 | double t1=(-B+mysqrt(delta))/(2*A); 276 | double t2=(-B-mysqrt(delta))/(2*A); 277 | vi.push_back(point(a.x+t1*dx,a.y+t1*dy)); 278 | if(sgn(delta)>0)vi.push_back(point(a.x+t2*dx,a.y+t2*dy)); 279 | } 280 | return vi; 281 | } 282 | double sector_area(point a,point b,double r){ 283 | double ang=atan2(a.y,a.x)-atan2(b.y,b.x); 284 | if(ang<0)ang+=2*PI; 285 | if(ang>2*PI)ang-=2*PI; 286 | return r*r*min(ang,2*PI-ang)/2; 287 | } 288 | double circle_cross_triangle(point a,point b,double r){ 289 | int ina=sgn(a.norm()-r)<0; 290 | int inb=sgn(b.norm()-r)<0; 291 | if(ina&&inb)return fabs(det(a,b))/2.0; 292 | vectorp=circle_cross_line(a,b,point(0,0),r); 293 | if(ina){ 294 | if(point_on_segment(p[0],a,b)==0)swap(p[0],p[1]); 295 | return sector_area(b,p[0],r)+fabs(det(a,p[0]))/2.0; 296 | } 297 | else if(inb){ 298 | if(point_on_segment(p[0],a,b)==0)swap(p[0],p[1]); 299 | return sector_area(p[0],a,r)+fabs(det(p[0],b))/2.0; 300 | } 301 | else{ 302 | if(p.size()==2&&point_on_segment(p[0],a,b)&&point_on_segment(p[1],a,b)){ 303 | if((a-p[0]).norm()>(a-p[1]).norm())swap(p[0],p[1]); 304 | return sector_area(a,p[0],r)+sector_area(p[1],b,r)+fabs(det(p[0],p[1]))/2.0; 305 | } 306 | else return sector_area(a,b,r); 307 | } 308 | } 309 | double circle_cross_polygon(int n,point *p,point o,double r){ 310 | double res=0; 311 | p[n+1]=p[1]; 312 | for(int i=1;i<=n;i++){ 313 | int tmp=sgn(det(p[i]-o,p[i+1]-o)); 314 | if(tmp)res+=tmp*circle_cross_triangle(p[i]-o,p[i+1]-o,r); 315 | } 316 | return fabs(res); 317 | } 318 | ``` 319 | 320 | ## 5. 二维凸包 321 | 322 | - 求上凸包,按坐标 (x, y) 字典升序排序,从小到大加入栈,如果出现凹多边形情况则出栈。下凸包反着来 323 | - $O(n\log n)$,排序是瓶颈 324 | 325 | ```cpp 326 | vector st; 327 | void push(vec &v, int b) { 328 | while ((int)st.size() > b 329 | && cross(st.end()[-2], st.back(), v) <= 0) // 会得到逆时针的凸包 330 | st.pop_back(); 331 | st.push_back(v); 332 | } 333 | void convex(vec a[], int n) { 334 | st.clear(); 335 | sort(a, a + n, [](vec a, vec b) { 336 | return make_pair(a.x, a.y) < make_pair(b.x, b.y); 337 | }); 338 | repeat (i, 0, n) push(a[i], 1); 339 | int b = st.size(); 340 | repeat_back (i, 0, n - 1) push(a[i], b); // repeat_back自动变成上凸包 341 | st.pop_back(); // 可能要对 st.size() <= 2 特判 342 | } 343 | ``` 344 | 345 | ### 5.1. <补充> 动态凸包 346 | 347 | - 支持添加点、询问点是否在凸包内,$O(\log n)$ 348 | 349 | ```cpp 350 | const lf eps=1e-7; 351 | multiset> st; vec c; 352 | typedef multiset>::iterator ptr; 353 | void push(vec v){ 354 | st.insert({v.theta(),v}); 355 | } 356 | void dec(ptr &p){if(p==st.begin())p=st.end(); --p;} 357 | void inc(ptr &p){++p; if(p==st.end())p=st.begin();} 358 | ptr find(vec v){ 359 | auto p=st.lower_bound({v.theta(),v}); dec(p); 360 | return p; 361 | } 362 | bool out(vec v){ // whether out of the convex 363 | v=v-c; 364 | auto l=find(v),r=l; inc(r); 365 | return cross(l->se,r->se,v)<-eps; 366 | } 367 | void init(vec v1,vec v2,vec v3){ 368 | st.clear(); 369 | c=(v1+v2+v3)*(1.0/3); 370 | push(v1-c); push(v2-c); push(v3-c); 371 | } 372 | void add(vec v){ // add a point to convex 373 | if(!out(v))return; 374 | v=v-c; 375 | auto l=find(v),r=l; inc(r); 376 | auto l2=l; dec(l2); 377 | while(cross(l->se,l2->se,v)>0){ 378 | dec(l),dec(l2); 379 | } 380 | auto r2=r; inc(r2); 381 | while(cross(r->se,r2->se,v)<0){ 382 | inc(r),inc(r2); 383 | } 384 | inc(l); 385 | if(l<=r)st.erase(l,r); 386 | else st.erase(l,st.end()),st.erase(st.begin(),r); 387 | st.insert({v.theta(),v}); 388 | } 389 | ``` 390 | 391 | ## 6. 旋转卡壳 392 | 393 | - 每次找到凸包每条边的最远点,基于二维凸包,$O(n\log n)$ 394 | 395 | ```cpp 396 | lf calipers(vec a[],int n){ 397 | convex(a,n); // 凸包算法 398 | repeat(i,0,st.size())a[i]=st[i]; n=st.size(); 399 | lf ans=0; int p=1; a[n]=a[0]; 400 | repeat(i,0,n){ 401 | while(cross(a[p],a[i],a[i+1]) a; // 存放点 421 | int l,w; 422 | int ans=0; 423 | void work(int i){ 424 | int u=w,d=0; 425 | repeat(k,i+1,a.size()) 426 | if(a[k].y>d && a[k].ya[i].y?u:d)=a[k].y; // 更新u和d 430 | if((l-a[i].x)*(u-d)<=ans)return; // 最优性剪枝 431 | } 432 | ans=max(ans,(l-a[i].x)*(u-d)); // 撞墙更新ans 433 | } 434 | int query(){ 435 | a.push_back(vec(0,0)); 436 | a.push_back(vec(l,w)); // 加两个点方便处理 437 | // 小矩形的左边靠着顶点的情况 438 | sort(a.begin(),a.end(),[](vec a,vec b){return a.xans)break; 480 | upd(a[i],b[j]); 481 | } 482 | b[t++]=a[i]; 483 | } 484 | } 485 | lf nearest(int n){ 486 | ans=1e20; 487 | sort(a,a+n,[](vec a,vec b){return a.xr+eps; 502 | } 503 | cir(vec a){v=a; r=0;} 504 | cir(vec a,vec b){v=(a+b)*0.5; r=(v-a).len();} 505 | cir(vec a,vec b,vec c){ // 三个点的外接圆 506 | b=b-a,c=c-a; 507 | vec s=vec(b.sqr(),c.sqr())*0.5; 508 | lf d=1/cross(b,c); 509 | v=a+vec(s.x*c.y-s.y*b.y,s.y*b.x-s.x*c.x)*d; 510 | r=(v-a).len(); 511 | } 512 | }; 513 | cir RIA(vec a[],int n){ 514 | repeat_back(i,2,n)swap(a[rand()%i],a[i]); // random_shuffle(a,a+n); 515 | cir c=cir(a[0]); 516 | repeat(i,1,n)if(c.out(a[i])){ 517 | c=cir(a[i]); 518 | repeat(j,0,i)if(c.out(a[j])){ 519 | c=cir(a[i],a[j]); 520 | repeat(k,0,j)if(c.out(a[k])) 521 | c=cir(a[i],a[j],a[k]); 522 | } 523 | } 524 | return c; 525 | } 526 | ``` 527 | 528 | ## 10. 半面交 using S&I 算法 529 | 530 | - 编号从 0 开始,$O(n\log n)$ 531 | 532 | ```cpp 533 | vector ans; // ans: output, shows a convex hull 534 | namespace half{ 535 | line a[N]; int n; // (a[],n): input, the final area will be the left of the lines 536 | deque q; 537 | void solve(){ 538 | a[n++]=line(vec(inf,inf),vec(-inf,inf)); 539 | a[n++]=line(vec(-inf,inf),vec(-inf,-inf)); 540 | a[n++]=line(vec(-inf,-inf),vec(inf,-inf)); 541 | a[n++]=line(vec(inf,-inf),vec(inf,inf)); 542 | sort(a,a+n,[](line a,line b){ 543 | if(a.th1 && !a[i].contain(r[0].PI(r[1])))q.pop_back(); 552 | while(q.size()>1 && !a[i].contain(q[0].PI(q[1])))q.pop_front(); 553 | q.push_back(a[i]); 554 | } 555 | while(q.size()>1 && !q[0].contain(r[0].PI(r[1])))q.pop_back(); 556 | while(q.size()>1 && !r[0].contain(q[0].PI(q[1])))q.pop_front(); 557 | #undef r 558 | ans.clear(); 559 | repeat(i,0,(int)q.size()-1)ans< e; 652 | int dist(int x,int y){ 653 | return abs(a[x].x-a[y].x)+abs(a[x].y-a[y].y); 654 | } 655 | #define lb(x) (x&-x) 656 | struct BIT{ // special 657 | int t[N]; 658 | void init(){ 659 | fill(t,t+n+1,0); 660 | } 661 | void insert(int x,int p){ 662 | for(;x<=n;x+=lb(x)) 663 | if(w[p]<=w[t[x]]) 664 | t[x]=p; 665 | } 666 | int query(int x){ 667 | int ans=0; 668 | for(;x!=0;x-=lb(x)) 669 | if(w[t[x]]<=w[ans]) 670 | ans=t[x]; 671 | return ans; 672 | } 673 | }bit; 674 | void work(){ 675 | bit.init(); 676 | repeat(i,1,n+1)c[i]=b[i].y; sort(c+1,c+n+1); 677 | sort(b+1,b+n+1,[](node a,node b){ 678 | return pii(a.x,a.y) ovo,e; 728 | vector > rec[N]; 729 | vector lab[N]; int labcnt,ans; 730 | void segunion(vector > &a){ // 区间合并至最简 731 | if(a.empty())return; 732 | sort(a.begin(),a.end()); int pre=0; 733 | repeat(i,0,a.size()){ 734 | if(a[i].fi>a[pre].se-eps)a[++pre]=a[i]; 735 | else a[pre].se=max(a[pre].se,a[i].se); 736 | } 737 | a.erase(a.begin()+pre+1,a.end()); 738 | } 739 | void segcomplement(vector > &a){ // 区间取反 740 | a.push_back(pair(0,inf)); 741 | repeat_back(i,0,a.size()-1){ 742 | a[i+1].fi=a[i].se; 743 | a[i].se=a[i].fi; 744 | } 745 | a[0].fi=-inf; 746 | } 747 | void Solve(){ 748 | int n=read(); ovo.clear(); e.clear(); labcnt=ans=0; 749 | repeat(i,0,n){ 750 | a[i].v.x=read(),a[i].v.y=read(),a[i].r=read(); 751 | ovo.push_back(a[i].v.y-a[i].r); 752 | ovo.push_back(a[i].v.y+a[i].r); 753 | } 754 | repeat(i,0,n) 755 | repeat(j,i+1,n) 756 | if((a[i].v-a[j].v).len()(a[i].v.x-d,a[i].v.x+d)); 772 | } 773 | segunion(rec[j]); 774 | segcomplement(rec[j]); 775 | lab[j].assign(rec[j].size(),0); 776 | repeat(i,0,lab[j].size())lab[j][i]=labcnt++; 777 | } 778 | d.init(labcnt); 779 | repeat(i,0,e.size()-1){ 780 | unsigned p1=0,p2=0; 781 | while(p1rec[i+1][p2].fi-eps && rec[i][p1].fi::iterator c; 826 | edge(int id=0){this->id=id;} 827 | }; 828 | int cmp(lf v){return abs(v)>eps?(v>0?1:-1):0;} 829 | lf cross(const vec &o,const vec &a,const vec &b){ 830 | return(a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x); 831 | } 832 | lf dot(const vec3D &a,const vec3D &b){return a.x*b.x+a.y*b.y+a.z*b.z;} 833 | vec3D cross(const vec3D &a,const vec3D &b){ 834 | return vec3D(a.y*b.z-a.z*b.y,-a.x*b.z+a.z*b.x,a.x*b.y-a.y*b.x); 835 | } 836 | vector ans; // 三角剖分结果 837 | struct DT{ // 使用方法:直接solve() 838 | list a[N]; vec v[N]; int n; 839 | void solve(int _n,vec _v[]){ 840 | n=_n; 841 | repeat (i, 0, n) a[i].clear(); 842 | copy(_v,_v+n,v); 843 | sort(v,v+n); 844 | divide(0,n-1); 845 | ans.clear(); 846 | for(int i=0;i0 && 862 | cmp(cross(c,a,d))*cmp(cross(c,d,b))>0; 863 | } 864 | void addedge(int u,int v){ 865 | a[u].push_front(edge(v)); 866 | a[v].push_front(edge(u)); 867 | a[u].begin()->c=a[v].begin(); 868 | a[v].begin()->c=a[u].begin(); 869 | } 870 | void divide(int l,int r){ 871 | if(r-l<=2){ 872 | for(int i=l;i<=r;i++) 873 | for(int j=i+1;j<=r;j++)addedge(i,j); 874 | return; 875 | } 876 | int mid=(l+r)/2; 877 | divide(l,mid); divide(mid+1,r); 878 | int nowl=l,nowr=r; 879 | for(int update=1;update;){ 880 | update=0; 881 | vec vl=v[nowl],vr=v[nowr]; 882 | for(auto i:a[nowl]){ 883 | vec t=v[i.id]; 884 | lf v=cross(vr,vl,t); 885 | if(cmp(v)>0 || (cmp(v)== 0 && vr.dist2(t)0 && (ch==-1 || incircle(vl,vr,v[ch],v[i.id])<0)){ 906 | ch=i.id,side=-1; 907 | } 908 | for(auto i:a[nowr]) 909 | if(cmp(cross(vr,v[i.id],vl))>0 && (ch==-1 || incircle(vl,vr,v[ch],v[i.id])<0)){ 910 | ch=i.id,side=1; 911 | } 912 | if(ch==-1)break; 913 | if(side==-1){ 914 | for(auto it=a[nowl].begin();it!=a[nowl].end();){ 915 | if(intersection(vl,v[it->id],vr,v[ch])){ 916 | a[it->id].erase(it->c); 917 | a[nowl].erase(it++); 918 | } 919 | else it++; 920 | } 921 | nowl=ch; 922 | addedge(nowl,nowr); 923 | } 924 | else{ 925 | for(auto it=a[nowr].begin();it!=a[nowr].end();){ 926 | if(intersection(vr,v[it->id],vl,v[ch])){ 927 | a[it->id].erase(it->c); 928 | a[nowr].erase(it++); 929 | } 930 | else it++; 931 | } 932 | nowr=ch; 933 | addedge(nowl,nowr); 934 | } 935 | } 936 | } 937 | }dt; 938 | ``` 939 | 940 | - 可以求最小生成树 941 | 942 | ```cpp 943 | vec a[N]; DSU d; 944 | vector e[N]; // 最小生成树结果 945 | void MST(){ // 求最小生成树 946 | dt.solve(n,a); 947 | sort(ans.begin(),ans.end(),[](const pii &A,const pii &B){ 948 | return a[A.fi].dist2(a[A.se]) r) return; 1011 | vec delta = (l2 - l1).trunc(sqrt(r * r - dis * dis)); 1012 | vec mid = perpendicular_pl(o, l1, l2); 1013 | res1 = mid + delta; 1014 | res2 = mid - delta; 1015 | } 1016 | ``` 1017 | 1018 | ## 17. 三维凸包 1019 | 1020 | - 将所有凸包上的面放入面集 `f` 中,其中 `face::p[i]` 作为 `a` 的下标,$O(n^2)$ 1021 | 1022 | ```cpp 1023 | const lf eps=1e-9; 1024 | struct vec{ 1025 | lf x,y,z; 1026 | vec(lf x=0,lf y=0,lf z=0):x(x),y(y),z(z){}; 1027 | vec operator-(vec b){return vec(x-b.x,y-b.y,z-b.z);} 1028 | lf len(){return sqrt(x*x+y*y+z*z);} 1029 | void shake(){ // 微小扰动 1030 | x+=(rand()*1.0/RAND_MAX-0.5)*eps; 1031 | y+=(rand()*1.0/RAND_MAX-0.5)*eps; 1032 | z+=(rand()*1.0/RAND_MAX-0.5)*eps; 1033 | } 1034 | }a[N]; 1035 | vec cross(vec a,vec b){ 1036 | return vec( 1037 | a.y*b.z-a.z*b.y, 1038 | a.z*b.x-a.x*b.z, 1039 | a.x*b.y-a.y*b.x); 1040 | } 1041 | lf dot(vec a,vec b){return a.x*b.x+a.y*b.y+a.z*b.z;} 1042 | struct face{ 1043 | int p[3]; 1044 | vec normal(){ // 法向量 1045 | return cross(a[p[1]]-a[p[0]],a[p[2]]-a[p[0]]); 1046 | } 1047 | lf area(){return normal().len()/2.0;} 1048 | }; 1049 | vector f; 1050 | bool see(face f,vec v){ 1051 | return dot(v-a[f.p[0]],f.normal())>0; 1052 | } 1053 | void convex(vec a[],int n){ 1054 | static vector c; 1055 | static bool vis[N][N]; 1056 | repeat(i,0,n)a[i].shake(); // 防止四点共面 1057 | f.clear(); 1058 | f.push_back((face){0,1,2}); 1059 | f.push_back((face){0,2,1}); 1060 | repeat(i,3,n){ 1061 | c.clear(); 1062 | repeat(j,0,f.size()){ 1063 | bool t=see(f[j],a[i]); 1064 | if(!t) // 加入背面 1065 | c.push_back(f[j]); 1066 | repeat(k,0,3){ 1067 | int x=f[j].p[k],y=f[j].p[(k+1)%3]; 1068 | vis[x][y]=t; 1069 | } 1070 | } 1071 | repeat(j,0,f.size()) 1072 | repeat(k,0,3){ 1073 | int x=f[j].p[k],y=f[j].p[(k+1)%3]; 1074 | if(vis[x][y] && !vis[y][x]) // 加入新面 1075 | c.push_back((face){x,y,i}); 1076 | } 1077 | f.swap(c); 1078 | } 1079 | } 1080 | ``` 1081 | -------------------------------------------------------------------------------- /Others.md: -------------------------------------------------------------------------------- 1 | # 杂项 2 | 3 | - [1. 语言](#1-语言) 4 | - [1.1. C++11](#11-c11) 5 | - [1.1.1. 初始代码](#111-初始代码) 6 | - [1.1.2. 如果没有万能头](#112-如果没有万能头) 7 | - [1.1.3. 容器](#113-容器) 8 | - [1.1.4. 其他语法](#114-其他语法) 9 | - [1.1.5. 神奇特性](#115-神奇特性) 10 | - [1.2. Java](#12-java) 11 | - [1.3. Python](#13-python) 12 | - [2. 常规算法](#2-常规算法) 13 | - [2.1. 算法基础](#21-算法基础) 14 | - [2.2. 离散化](#22-离散化) 15 | - [2.3. 01 分数规划](#23-01-分数规划) 16 | - [2.4. 任务规划](#24-任务规划) 17 | - [2.4.1. Livshits-Kladov 定理](#241-livshits-kladov-定理) 18 | - [2.4.2. Johnson 规则](#242-johnson-规则) 19 | - [2.5. 分治](#25-分治) 20 | - [2.5.1. 逆序数 using 二维偏序](#251-逆序数-using-二维偏序) 21 | - [2.6. 反悔贪心](#26-反悔贪心) 22 | - [2.7. 线段树分治](#27-线段树分治) 23 | - [2.8. 最大空矩阵 using 悬线法](#28-最大空矩阵-using-悬线法) 24 | - [2.9. 搜索](#29-搜索) 25 | - [2.9.1. 舞蹈链 / DLX](#291-舞蹈链--dlx) 26 | - [2.9.2. 启发式算法](#292-启发式算法) 27 | - [2.10. 动态规划](#210-动态规划) 28 | - [2.10.1. 多重背包](#2101-多重背包) 29 | - [2.10.2. 最长不降子序列 / LIS](#2102-最长不降子序列--lis) 30 | - [2.10.3. 数位 dp](#2103-数位-dp) 31 | - [2.10.4. 换根 dp](#2104-换根-dp) 32 | - [2.10.5. 斜率优化](#2105-斜率优化) 33 | - [2.10.6. 四边形优化](#2106-四边形优化) 34 | - [2.11. 哈希 / Hash](#211-哈希--hash) 35 | - [2.11.1. 字符串哈希](#2111-字符串哈希) 36 | - [2.11.2. 质因数哈希](#2112-质因数哈希) 37 | - [3. 字符串](#3-字符串) 38 | - [3.1. 字符串函数](#31-字符串函数) 39 | - [3.1.1. 前缀函数 using kmp](#311-前缀函数-using-kmp) 40 | - [3.1.2. z 函数 using exkmp](#312-z-函数-using-exkmp) 41 | - [3.1.3. 马拉车 / Manacher](#313-马拉车--manacher) 42 | - [3.1.4. 最小表示法](#314-最小表示法) 43 | - [3.1.5. 后缀数组 / SA](#315-后缀数组--sa) 44 | - [3.1.6. height数组](#316-height数组) 45 | - [3.2. 自动机](#32-自动机) 46 | - [3.2.1. 字典树 / Trie](#321-字典树--trie) 47 | - [3.2.2. AC 自动机](#322-ac-自动机) 48 | - [3.2.3. 后缀自动机 / SAM](#323-后缀自动机--sam) 49 | - [4. 技巧](#4-技巧) 50 | - [4.1. 位运算](#41-位运算) 51 | - [4.1.1. 位运算操作](#411-位运算操作) 52 | - [4.1.2. 枚举二进制子集](#412-枚举二进制子集) 53 | - [4.2. 浮点数](#42-浮点数) 54 | - [4.3. 常数优化 / 卡常](#43-常数优化--卡常) 55 | - [4.3.1. 指令集优化](#431-指令集优化) 56 | - [4.3.2. 快读快写](#432-快读快写) 57 | - [4.3.3. STL 手写内存分配器](#433-stl-手写内存分配器) 58 | - [4.3.4. 取模卡常](#434-取模卡常) 59 | - [4.3.5. 其他优化](#435-其他优化) 60 | - [4.4. 扩栈](#44-扩栈) 61 | - [4.5. 卡时暴力](#45-卡时暴力) 62 | - [4.6. 对拍 / bat](#46-对拍--bat) 63 | - [4.7. 战术分析](#47-战术分析) 64 | 65 | ## 1. 语言 66 | 67 | ### 1.1. C++11 68 | 69 | #### 1.1.1. 初始代码 70 | 71 | ```cpp 72 | // #pragma GCC optimize(3) 73 | #include 74 | using namespace std; 75 | #define repeat(i, a, b) for (int i = (a), ib = (b); i < ib; i++) 76 | #define repeat_back(i, a, b) for (int i = (b) - 1, ib = (a);i >= ib; i--) 77 | #define fi first 78 | #define se second 79 | // #define int ll 80 | namespace start { 81 | typedef long long ll; const int inf = INT_MAX >> 1; const ll INF = INT64_MAX >> 1; 82 | typedef double lf; const lf pi = acos(-1); typedef pair pii; 83 | mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count()); 84 | const int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1}, dy[8] = {1, -1, 0, 0, 1, -1, 1, -1}; 85 | template ll read() { // will detect EOF, set fast = 0 in Hangdian OJ 86 | if (!fast) { ll x; if (scanf("%lld", &x) != 1) exit(0); return x; } 87 | ll x = 0, tag = 1; int c = getchar(); for (; !isdigit(c); c = getchar()) { if (c == '-') tag = -1; if (c == EOF) exit(0); } 88 | for (; isdigit(c); c = getchar()) { x = x * 10 + c - 48; } if (c != EOF) ungetc(c, stdin); 89 | return x * tag; 90 | } 91 | lf readf() { double x; if (scanf("%lf", &x) != 1) exit(0); return x; } // will detect EOF 92 | template void write(T x) { if (x < 0) { x = -x, putchar('-'); } if (x >= 10) { write(x / 10); } putchar(x % 10 + 48); } 93 | void write(double x) { printf("%.12f", x); } void write(long double x) { printf("%.12f", double(x)); } 94 | template void write(const pair &x) { write(x.first); putchar(' '); write(x.second); } 95 | template void print(T x, int e = 1) { write(x); putchar(" \n"[e]); } 96 | template void print(const initializer_list &a, int e = 1) { for (auto i = a.begin(), last = prev(a.end()); i != a.end(); i++) print(*i, e && i == last); } 97 | template void printArray(const T &a, int e = 1) { for (auto i = a.begin(), last = prev(a.end()); i != a.end(); i++) print(*i, e && i == last); } 98 | template void mst(TempT (&a)[TempN], T el) { fill_n(reinterpret_cast(a), sizeof(a) / sizeof(T), el); } 99 | template T &MAX(T &a, const T &b) { if (a < b) a = b; return a; } template T &MIN(T &a, const T &b) { if (b < a) a = b; return a; } 100 | template vector &operator<<(vector &a, const T &b) { a.push_back(b); return a; } 101 | template T sqr(const T &x) { return x * x; } 102 | template void sortunique(T &a) { sort(a.begin(), a.end()); a.erase(unique(a.begin(), a.end()), a.end()); } 103 | void OK(const char *msg = "Yes") { puts(msg); throw 0; } void GG(const char *msg = "No") { puts(msg); throw 0; } 104 | // int cansel_sync = (ios::sync_with_stdio(0), cin.tie(0), 0); 105 | const int N = 500010; 106 | const lf eps = 1e-9; 107 | const int mod = (1 ? 1000000007 : 998244353); 108 | int D(int x, int m = mod) { return x >= m ? x - m : x; } 109 | int mul(int a, int b, int m = mod) { return 1ll * a * b % m; } 110 | template int qpow(int a, T b, int m = mod) { 111 | int ans = 1; 112 | for (; b; a = mul(a, a, m), b >>= 1) if (b & 1) 113 | ans = mul(ans, a, m); 114 | return ans; 115 | } 116 | } using namespace start; 117 | const int debug = 1; 118 | void Solve() { 119 | } 120 | signed main() { 121 | // freopen("data.txt", "r", stdin); 122 | int T = 1; // T = read(); 123 | repeat (ca, 1, T + 1) { 124 | try { Solve(); } catch (signed) { } 125 | } 126 | return 0; 127 | } 128 | ``` 129 | 130 | ```cpp 131 | struct Comb { 132 | static const int N = 1000010; 133 | int fac[N], inv[N]; 134 | Comb() { 135 | fac[0] = 1; 136 | repeat (i, 1, N) 137 | fac[i] = 1ll * fac[i - 1] * i % mod; 138 | inv[N - 1] = qpow(fac[N - 1], mod - 2, mod); 139 | repeat_back (i, 1, N) 140 | inv[i - 1] = 1ll * inv[i] * i % mod; 141 | } 142 | int operator()(int a, int b) { // a >= b 143 | if (a < b || b < 0) return 0; 144 | return 1ll * fac[a] * inv[a - b] % mod * inv[b] % mod; 145 | } 146 | int A(int a, int b) { // a >= b 147 | if (a < b || b < 0) return 0; 148 | return 1ll * fac[a] * inv[a - b] % mod; 149 | } 150 | } C; 151 | struct Pow { 152 | int p[N]; 153 | Pow(int a) { 154 | p[0] = 1; 155 | repeat (i, 1, N) p[i] = 1ll * p[i - 1] * a % mod; 156 | } 157 | int operator[](int n) { return p[n]; } 158 | }; 159 | ``` 160 | 161 | #### 1.1.2. 如果没有万能头 162 | 163 | ```cpp 164 | #include 165 | #include 166 | #include 167 | #include 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | #include 174 | #include 175 | #include 176 | #include 177 | #include 178 | #include 179 | // #include 180 | // #include 181 | // #include 182 | ``` 183 | 184 | 其他定义 185 | 186 | ```cpp 187 | #define inline __inline __attribute__((always_inline)) 188 | // struct name{bool operator()(const type &x,const type &y){return func(x,y);}} 189 | typedef long double llf; 190 | #define endl "\n" 191 | ``` 192 | 193 | #### 1.1.3. 容器 194 | 195 | 平板电视红黑树 196 | 197 | - 其实就是一个支持排名的 set/map,改造成 multiset/multimap 需要加个时间戳 198 | - 第二个参数 null_type 表示这是个 set,如果想要 map 可以换成其他类型 199 | - 第四个参数可以改成 splay_tree_tag(然后就不是红黑树了) 200 | - 第五个参数表示支持排名(tree_policy.hpp 只找到这一个类) 201 | 202 | ```cpp 203 | #include 204 | #include 205 | using namespace __gnu_pbds; 206 | tree,rb_tree_tag,tree_order_statistics_node_update> t; // 定义 207 | t.insert({x,i+1}); // ----------------- 插入 x,用时间戳 i+1 标注(因为不是 multiset) 208 | t.erase(t.lower_bound({x,0})); // ----- 删除 x(删除单个元素) 209 | t.order_of_key({x,0})+1; // ----------- x 的排名(小于 x 的元素个数 + 1) 210 | t.find_by_order(x-1)->first; // ------- 排名为 x 的元素(第 x 小的数) 211 | prev(t.lower_bound({x,0}))->first; // - x 的前驱(小于x且最大) 212 | t.lower_bound({x+1,0})->first; // ----- x 的后继(大于x且最小) 213 | t.join(t2); // ------------------------ 将 t2 并入 t, t2 清空,前提是取值范围不相交 214 | t.split(v,t2); // --------------------- 小于等于 v 的元素属于 t,其余的属于 t2 215 | ``` 216 | 217 | 平板电视优先队列 218 | 219 | - `pairing_heap_tag` 配对堆,应该是可并堆里最快的 220 | - `thin_heap_tag` 斐波那契堆 221 | - `std::priority_queue` 不合并就很快 222 | 223 | ```cpp 224 | #include 225 | using namespace __gnu_pbds; 226 | __gnu_pbds::priority_queue,pairing_heap_tag> h; // 大根堆 227 | h.push(x); h.top(); h.pop(); 228 | h.join(h2); // 将h2并入h,h2清空 229 | ``` 230 | 231 | rope 232 | 233 | - 可能是可分裂平衡树 234 | 235 | ```cpp 236 | #include 237 | using namespace __gnu_cxx; 238 | rope r; // 块状链表 239 | r.push_back(n); 240 | r.insert(pos,n); // 插入一个元素 241 | r.erase(pos,len); // 区间删除 242 | r.copy(pos,len,x); // 区间赋值到x 243 | r.replace(pos,x); // 相当于r[pos]=x; 244 | r.substr(pos,len); // 这是啥不会用 245 | r[pos] // 只能访问不能修改 246 | r.clear(); 247 | rope *his[N]; his[0]=new rope(); his[i]=new rope(*his[i-1]); // 据说O(1)拷贝,一行可持久化 248 | ``` 249 | 250 | #### 1.1.4. 其他语法 251 | 252 | STL 253 | 254 | ```cpp 255 | a=move(b); // 容器移动(a赋值为b,b清空) 256 | priority_queue(begin,end) // O(n)建堆 257 | ``` 258 | 259 | ```cpp 260 | a.find(key) // set,map查找,没找到返回a.end() 261 | a.lower_bound(key) // set,map限制最小值 262 | a.insert(b.begin(),b.end()); // set,map合并(时间复杂度极高) 263 | a.emplace_hint(it,b); // 把插入b,如果位置恰好为it就会很快 264 | ``` 265 | 266 | ```cpp 267 | complex c; complex c(1,2);// 复数 268 | c.real(),c.imag() // 实部、虚部 269 | ``` 270 | 271 | ```cpp 272 | bitset<32> b; // 声明一个32位的bitset 273 | b[n]; b[n]=1; // 访问和修改 274 | b.none(); // 返回是否为空 275 | b.count(); // 返回1的个数 276 | b.to_ullong(); b.to_string(); // 转换 277 | b._Find_first(); // 最低位 1 的下标 278 | b._Find_next(pos); // 位置严格大于 pos 的最低位 1 的下标(不存在就返回 size()) 279 | ``` 280 | 281 | unordered 容器手写 hash 函数 282 | 283 | ```cpp 284 | struct myhash{ 285 | typedef unsigned long long ull; 286 | ull f(ull x)const{ 287 | x+=0x321354564536; // 乱敲 288 | x=(x^(x>>30))*0x3212132123; // 乱敲 289 | return x^(x>>31); 290 | } 291 | ull operator()(pii x)const{ 292 | static ull t=chrono::steady_clock::now().time_since_epoch().count(); 293 | return f(x.fi+t)^f(x.se+t*2); 294 | } 295 | }; 296 | unordered_set a; 297 | ``` 298 | 299 | cmath 300 | 301 | ```cpp 302 | fmod(x) // 浮点取模 303 | tgamma(x) // 计算Γ(x) 304 | atan2(x,y) // 计算坐标(x,y)的极角 305 | hypot(x,y) // 计算sqrt(x^2+y^2) 306 | ``` 307 | 308 | 读入 309 | 310 | ```cpp 311 | getline(cin, [string]); // 读一行 312 | cin.getline(s, sizeof s); // 读一行 313 | cin.putback([char]); ungetc([char], stdin); // 将字符放入缓冲区 314 | cin.unget(); // 退回一个字符 315 | void gets(char s[]) { 316 | for (int i = 0;; i++) { 317 | s[i] = getchar(); 318 | if (s[i] == '\n' || s[i] == -1) { s[i] = 0; return; } 319 | } 320 | } 321 | ``` 322 | 323 | scanf字符串正则化 324 | 325 | ```cpp 326 | scanf("%ns",str); // 读入n个字符 327 | scanf("%[a-z]",str); // 遇到非小写字母停止 328 | scanf("%[^0-9]",str); // 遇到数字停止,^表示非 329 | scanf("%*[a-z]"); // 也是遇到非小写字母停止,只不过不读入字符串 330 | ``` 331 | 332 | #### 1.1.5. 神奇特性 333 | 334 | - 在64位编译器(我的编译器)中set每个元素需要额外32字节内存。 335 | - 命名空间rel_ops:之后只定义小于就能用其他所有次序关系符号 336 | - raw strings:`R"(abc\n)"` 相当于 `"abc\\n"` 337 | - 定义数字开头的变量:`type operator ""_name(type number){/*...*/}`(之后 `1_name` 即把1带入上述函数中,参数类型只能是`ull,llf,char,(const char *,size_t)`) 338 | - 高级宏:`__VA_ARGS__`是参数列表(对应`...`),`__LINE__` 是当前行数,`__FUNCTION__`是当前函数名,`__COUNTER__`是宏展开次数-1 339 | - 位域:`struct{int a:3;};` 表示struct里a占3 bit,可以节省空间 340 | - %n:`scanf,printf` 中 %n 将读入/输出的字符个数写入变量 341 | 342 | ### 1.2. Java 343 | 344 | ```java 345 | import java.util.*; 346 | import java.math.BigInteger; 347 | import java.math.BigDecimal; 348 | public class Main { 349 | static Scanner sc = new Scanner(System.in); 350 | public static void main(String[] args) { 351 | } 352 | } 353 | ``` 354 | 355 | ```java 356 | import java.util.*; 357 | import java.io.*; 358 | import java.math.BigInteger; 359 | import java.math.BigDecimal; 360 | public class Main { 361 | static Scanner cin = new Scanner(System.in); 362 | static PrintStream cout = System.out; 363 | public static void main(String[] args) { 364 | } 365 | } 366 | ``` 367 | 368 | - 编译运行 `java Main.java` 369 | - 编译 `javac Main.java` // 生成 Main.class 370 | - 运行 `java Main` 371 | 372 | 数据类型 373 | 374 | ```java 375 | int // 4字节有符号 376 | long // 8字节有符号 377 | double, boolean, char, String 378 | ``` 379 | 380 | ```java 381 | final double PI = 3.14; // final 类似 c++ 的 const 382 | var n = 1; // var 类似 c++ 的 auto 383 | long 型常量结尾加 L,如 1L 384 | ``` 385 | 386 | 数组 387 | 388 | ```java 389 | int[] arr = new int[100]; // 数组(可以是变量) 390 | int[][] arr = new int[10][10]; // 二维数组 391 | arr.length; // 数组长度,没有括号 392 | Arrays.binarySearch(arr, l, r, x) // 在 arr[[l,r-1]] 中二分查找 x,若存在返回位置,不存在返回 -lowerbound-1 393 | Arrays.sort(arr, l, r); Arrays.sort(arr); // 对 arr[[l, r-1]] 排序 394 | Arrays.fill(arr, l, r, x); Arrays.fill(arr, x); // 填充 arr[[l, r-1]] 为x 395 | ``` 396 | 397 | 极其麻烦的结构体 compareTo 重载 398 | 399 | ```java 400 | public class Main { 401 | public static class node implements Comparable { 402 | int x; 403 | public node() {} 404 | public int compareTo(node b) { 405 | return x - b.x; 406 | } 407 | } 408 | static Scanner sc; 409 | public static void main(String[] args) { 410 | sc = new Scanner(System.in); 411 | int n = sc.nextInt(); 412 | node[] a = new node[n]; 413 | for (int i = 0; i < n; i++) { 414 | a[i] = new node(); 415 | a[i].x = sc.nextInt(); 416 | } 417 | Arrays.sort(a); 418 | for (node i : a) 419 | System.out.print(i.x + " "); 420 | System.out.println(); 421 | } 422 | } 423 | ``` 424 | 425 | 输出 426 | 427 | ```java 428 | System.out.print(x); 429 | System.out.println(); 430 | System.out.println(x); 431 | System.out.printf("%.2f\n", d); // 格式化 432 | ``` 433 | 434 | 输入 435 | 436 | ```java 437 | import java.util.Scanner; 438 | Scanner sc = new Scanner(System.in); // 初始化 439 | String s = sc.nextLine(); // 读一行字符串 440 | int n = sc.nextInt(); // 读整数 441 | double d = sc.nextDouble(); // 读实数 442 | sc.hasNext() // 是否读完 443 | ``` 444 | 445 | String 446 | 447 | ```java 448 | s1.equals(s2) // 返回是否相等 449 | s1.compareTo(s2) // s1 > s2 返回 1,s1 < s2 返回 -1,s1 == s2 返回 0 450 | s1.contains(s2) // 返回是否包含子串 s2 451 | s1.indexOf(s2, begin = 0) // 查找子串位置 452 | s1.substring(l, r) // 返回子串 [l,r-1] 453 | s1.charAt(x) // 类似 c++ 的 s1[x] 454 | s1.length() // 返回长度 455 | s1 + s2 // 返回连接结果 456 | String.format("%d", n) // 返回格式化结果 457 | ``` 458 | 459 | StringBuffer / StringBuilder 460 | 461 | ```java 462 | StringBuffer s1 = new StringBuffer(); 463 | StringBuffer s1 = new StringBuffer("A"); 464 | s1.append("A"); // 类似 c++ 的 s1+="A"; 465 | s1.reverse(); // 反转字符串 466 | s1.replace(l, r, "A"); // 将子串 [l, r - 1] 替换为 "A" (delete + insert) 467 | s1.charAt(x); // 类似 c++ 的 s1[x] 468 | s1.setCharAt(x, c); // 类似 c++ 的 s1[x] = c; 469 | ``` 470 | 471 | Math 472 | 473 | ```java 474 | // 不用 import 就能用下列函数 475 | Math.{sqrt, sin, atan, abs, max, min, pow, exp, log, PI, E} 476 | ``` 477 | 478 | Random 479 | 480 | ```java 481 | import java.util.Random; 482 | Random rnd = new Random(); // 已经把时间戳作为了种子 483 | rnd.nextInt(); 484 | rnd.nextInt(n); // [0, n) 485 | ``` 486 | 487 | BigInteger 488 | 489 | ```java 490 | import java.math.BigInteger; 491 | BigInteger n; 492 | new BigInteger("0"); // String 转换为 BigInteger 493 | BigInteger.valueOf(I) // int / long 转换为 BigInteger 494 | BigInteger.ZERO / ONE / TEN // 0 / 1 / 10 495 | sc.nextBigInteger() // 读入,括号里可以写进制 496 | BigInteger[] arr = new BigInteger[10]; 497 | n1.intValue / longValue() // 转换为 int / long(太大直接丢失高位,不报异常,报异常可以后面加 Exact) 498 | n1.add / subtract / multiply(n2) // 加 减 乘 499 | n1.divide / mod(n2) // 整除 取模 500 | n1.compareTo(n2) // n1 > n2 返回 1,n1 < n2 返回 -1,n1 == n2 返回 0 501 | n1.equals(n2) // 判断相等 502 | n1.abs() n1.pow(I) n1.gcd(n2) n1.max / min(n2) // int I 503 | n1.modPow(n2,n3) n1.modInverse(n2) // 快速幂 逆元(gcd(n1,n2)!=1) 504 | n1.toString(I) // 返回 I 进制字符串 505 | n1.and / or / xor / shiftLeft / shiftRight(n2) // 位运算 506 | n1.testBit(I) n1.setBit / clearBit / flipBit(I) // 取二进制第 I 位,操作第 I 位 507 | n1.bitCount() // 二进制 1 的个数 508 | // 运算时 n2 一定要转换成 BigInteger 509 | ``` 510 | 511 | BigDecimal 512 | 513 | ```java 514 | import java.math.BigDecimal; 515 | n1.divide(n2, 2, BigDecimal.ROUND_HALF_UP) // 保留 2 位(四舍五入) 516 | // 貌似没有 sqrt 等操作,都得自己实现 qwq 517 | ``` 518 | 519 | ### 1.3. Python 520 | 521 | 读入(EOF停止) 522 | 523 | ```python 524 | def Solve(): 525 | a,b=map(int,input().split()) 526 | print(a+b) 527 | while True: 528 | try: 529 | Solve() 530 | except EOFError: 531 | break 532 | ``` 533 | 534 | 读入(T组数据) 535 | 536 | ```python 537 | def Solve(): 538 | a,b=map(int,input().split()) 539 | print(a+b) 540 | T=int(input()) 541 | for ca in range(1,T+1): 542 | Solve() 543 | ``` 544 | 545 | 输出(不回车) 546 | 547 | ```python 548 | print(x,end="") 549 | ``` 550 | 551 | 常量 552 | 553 | ```python 554 | None # 空值 555 | True False # 布尔值 556 | ``` 557 | 558 | 字符串str 559 | 560 | ```python 561 | eval(s); # 表达式求值 562 | ord(c); chr(c); # 字符和编码的转换 563 | int("123"); str(123); # 数字与字符串的转换 564 | ``` 565 | 566 | 列表list 567 | 568 | ```python 569 | a=[]; a.append(x); a.pop(); a[x]; # 最后一个不存在会报错 570 | len(a); # 返回 size 571 | a.sort(); # 排序 572 | a.sort(reverse=True); # 降序排序 573 | a.sort(key=str); # 按照指定函数转换后比较 574 | ``` 575 | 576 | 字典dict 577 | 578 | ```python 579 | - a={}; a={x:y}; a[x]=y; a[x]; # 最后一个不存在会报错 580 | - a.get(x); # 不存在返回 None 581 | - a.pop(x); # 不存在会报错 582 | ``` 583 | 584 | 集合set 585 | 586 | ```python 587 | a=set(); a.add(x); a.remove(x); # 最后一个不存在会报错 588 | a&b; a|b; a-b; a^b; # 集合的交、并、差、对称差 589 | ``` 590 | 591 | 高级操作 592 | 593 | ```python 594 | enumerate() # 返回 list 的每个元素变成 (下标, 元素) 后的生成器 595 | zip(, ) # 返回两个 list 对应元素组成二元组的生成器 596 | ``` 597 | 598 | ## 2. 常规算法 599 | 600 | ### 2.1. 算法基础 601 | 602 | - STL自带算法 603 | 604 | ```cpp 605 | fill(begin,end,element); // 填充 606 | fill_n(begin,n,element); // 填充 607 | iota(begin,end,t); // 递增填充(赋值为t,t+1,t+2,...) 608 | copy(a_begin,a_end,b_begin); // 复制(注意会复制到b里) 609 | reverse(begin,end); // 翻转 610 | ``` 611 | 612 | ```cpp 613 | nth_element(begin,begin+k,end); // 将第k+1小置于位置k,平均O(n) 614 | binary_search(begin,end,key,[less]) // 返回是否存在 615 | upper_bound(begin,end,key,[less]) // 返回限制最小值地址 616 | lower_bound(begin,end,key,[less]) // 返回严格限制最小值地址 617 | merge(a_begin,a_end,b_begin,b_end,c_begin,[less]); // 归并a和b(结果存c) 618 | inplace_merge(begin,begin+k,end); // 归并(原地保存) 619 | next_permutation(begin,end); prev_permutation(begin,end); // 允许多重集,返回不到底,使用方法 do{/*...*/}while(next_permutation(begin,end)); 620 | min_element(begin,end); max_element(begin,end); // 返回最值的指针 621 | for_each(begin,end,work); // 每个元素进行work操作 622 | ``` 623 | 624 | ```cpp 625 | auto it=back_inserter(a); // it=x表示往a.push_back(x) 626 | ``` 627 | 628 | - 进制转换 629 | 630 | ```cpp 631 | strtol(str,0,base),strtoll // 返回字符数组的base进制数 632 | stol(s,0,base),stoll // 返回字符串的base进制数 633 | sscanf,sprintf // 十进制 634 | to_string(n) // 十进制 635 | ``` 636 | 637 | - 随机数 638 | 639 | ```cpp 640 | #include 641 | mt19937 rnd(time(0)); 642 | // 巨佬定义:mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count()); 643 | cout<m2)swap(m1,m2);} 687 | // m1即次大值 688 | ``` 689 | 690 | - 朴素 mex 691 | 692 | ```cpp 693 | struct Mex { 694 | int vis[N], dcnt; 695 | Mex() { dcnt = 1; } 696 | void push(int x) { vis[x] = dcnt; } 697 | int calc() { for (int i = 0; ; i++) if (vis[i] != dcnt) return dcnt++, i; } 698 | } mex; 699 | ``` 700 | 701 | - 快速排序 / 快排 702 | 703 | ```cpp 704 | void qsort(int l, int r) { 705 | if (l >= r) return; 706 | swap(a[l], a[l + rnd() % (r - l)]); 707 | int x = l, y = r, mid = a[l]; 708 | while (x < y) { 709 | while (x < y && a[y] >= mid) y--; 710 | swap(a[x], a[y]); 711 | while (x < y && a[x] < mid) x++; 712 | swap(a[x], a[y]); 713 | } 714 | qsort(l, x - 1); 715 | qsort(x + 1, r); 716 | } 717 | ``` 718 | 719 | ### 2.2. 离散化 720 | 721 | - 从小到大标号并赋值,$O(n\log n)$,~~是个好东西~~。 722 | 723 | ```cpp 724 | void disc(int a[],int n){ 725 | vector b(a,a+n); 726 | sort(b.begin(),b.end()); 727 | b.erase(unique(b.begin(),b.end()),b.end()); 728 | repeat(i,0,n) 729 | a[i]=lower_bound(b.begin(),b.end(),a[i])-b.begin(); // 从0开始编号 730 | } 731 | ``` 732 | 733 | ```cpp 734 | void disc(int a[],int n,int d){ // 把距离>d的拉近到d 735 | vector b(a,a+n); 736 | sort(b.begin(),b.end()); 737 | b.erase(unique(b.begin(),b.end()),b.end()); 738 | vector c(b.size()); c[0]=0; // 从0开始编号 739 | repeat(i,1,b.size()) 740 | c[i]=c[i-1]+min(d,b[i]-b[i-1]); 741 | repeat(i,0,n) 742 | a[i]=c[lower_bound(b.begin(),b.end(),a[i])-b.begin()]; 743 | } 744 | ``` 745 | 746 | ```cpp 747 | struct Disc{ // 离散化后a[]的值互不相同,但是a[i]与a[j]∈[d.pre[a[i]],d.nxt[a[i]]]在离散化前是相同的 748 | int b[N],pre[N],nxt[N]; 749 | void init(int a[],int n){ 750 | copy(a,a+n,b); sort(b,b+n); 751 | pre[0]=0; 752 | repeat(i,1,n){ 753 | if(b[i]==b[i-1])pre[i]=pre[i-1]; 754 | else pre[i]=i; 755 | } 756 | nxt[n-1]=n-1; 757 | repeat_back(i,0,n-1){ 758 | if(b[i]==b[i+1])nxt[i]=nxt[i+1]; 759 | else nxt[i]=i; 760 | } 761 | repeat(i,0,n){ 762 | a[i]=lower_bound(b,b+n,a[i])-b; 763 | b[a[i]]--; 764 | } 765 | } 766 | }d; 767 | ``` 768 | 769 | ### 2.3. 01 分数规划 770 | 771 | - n 个物品,都有两个属性 $a_i$ 和 $b_i$,任意取 k 个物品使它们的 $\dfrac {\sum a_j}{\sum b_j}$ 最大 772 | - 解:二分答案 773 | - m 是否满足条件即判断 $\dfrac {\sum a_j}{\sum b_j}\ge m$,即 $\sum(a_j-mb_j)\ge 0$ 774 | - 因此计算 $c_i=a_i-mb_i$ ,取前 k 个最大值看它们之和是否 $\ge 0$ 775 | - 如果限制条件是 $\sum b_j\ge W$,则将 $b_j$ 看成体积,$a_j-mb_j$ 看成价值,转换为背包dp 776 | 777 | ```cpp 778 | int n,k; lf a[N],b[N],c[N]; 779 | bool check(lf mid){ 780 | repeat(i,0,n)c[i]=a[i]-mid*b[i]; 781 | nth_element(c,c+k,c+n,greater()); 782 | lf sum=0; repeat(i,0,k)sum+=c[i]; 783 | return sum>=0; 784 | } 785 | lf solve(){ 786 | lf l=0,r=1; 787 | while(r-l>1e-9){ 788 | lf mid=(l+r)/2; 789 | if(check(mid))l=mid; else r=mid; 790 | } 791 | return l; 792 | } 793 | ``` 794 | 795 | ### 2.4. 任务规划 796 | 797 | #### 2.4.1. Livshits-Kladov 定理 798 | 799 | - 给出 n 个任务,第 i 个任务花费 $t_i$ 时间,该任务开始之前等待 t 时间的代价是 $f_i(t)$ 个数,求一个任务排列方式,最小化代价 $\sum_{i=1}^n f_j(\sum_{j=1}^{i-1}t_i)$ 800 | - Livshits-Kladov定理:当 $f_i(t)$ 是一次函数 / 指数函数 / 相同的单增函数时,最优解可以用排序计算 801 | - 一次函数:$f_i(t)=c_it+d_i$,按 $\dfrac {c_i}{t_i}$ 升序排列 802 | - 指数函数:$f_i(t)=c_ia^t+d_i$,按 $\dfrac{1-a^{t_i}}{c_i}$ 升序排列 803 | - 相同的单增函数:按 $t_i$ 升序排序 804 | 805 | #### 2.4.2. Johnson 规则 806 | 807 | - n 个任务和两个机器,每个任务必须先在 A 上做 $a_i$ 分钟再在 B 上做 $b_i$ 分钟,求最小时间。 808 | 809 | ```cpp 810 | sort(p,p+n,[](int x,int y){ 811 | int xx=a[x]>b[x],yy=a[y]>b[y]; 812 | if(xx!=yy)return xxb[y]; 815 | }); 816 | ``` 817 | 818 | ### 2.5. 分治 819 | 820 | #### 2.5.1. 逆序数 using 二维偏序 821 | 822 | - $O(n\log n)$ 823 | 824 | ```cpp 825 | void merge(int l, int r) { // include, exclude 826 | static int t[N]; 827 | if (r - l <= 1) return; 828 | int mid = l + (r - l) / 2; 829 | merge(l, mid); 830 | merge(mid, r); 831 | int p = l, q = mid, s = l; 832 | while (s < r) { 833 | if (p >= mid || (q < r && a[p] > a[q])) { 834 | t[s++] = a[q++]; 835 | ans += mid - p; // here 836 | } 837 | else 838 | t[s++] = a[p++]; 839 | } 840 | repeat (i, l, r) a[i] = t[i]; 841 | } 842 | ``` 843 | 844 | ### 2.6. 反悔贪心 845 | 846 | - 例:第 i 天要么获得 $A_i$ 元要么获得 $B_i$ 元和 1 积分,$A_i\ge 0$,$B_i$ 可能 $<0$,问获得积分最大值 847 | - 能拿 B 就拿 B,不然就把之前某个 B 换成 A 然后拿这次的 B(如果更优的话) 848 | 849 | ```cpp 850 | priority_queue q; 851 | int n=read(),now=0,ans=0; 852 | repeat(i,0,n){ 853 | int A=read(),B=read(); 854 | if(now+B>=0){ 855 | q.push(A-B); 856 | ans++; 857 | now+=B; 858 | } 859 | else if(!q.empty() && now+q.top()+B>=0 && q.top()>A-B){ 860 | now+=q.top()+B; 861 | q.pop(); 862 | q.push(A-B); 863 | } 864 | else now+=A; 865 | } 866 | ``` 867 | 868 | ### 2.7. 线段树分治 869 | 870 | - 支持添删边的离线操作 871 | - 对时间建立线段树,每个结点开一个vector。对一条边,添加到删除的时间区间,插入到线段树中。最后对线段树 DFS 一遍统计答案,向下走即添边,向上走即撤销,用可撤销数据结构维护 872 | - 复杂度为 $O(n\log n)$ 乘以可撤销数据结构复杂度 873 | - 如果元素对询问的贡献可以分开计算,那么只要对线段树每个节点进行统计即可,不需要可撤销数据结构 874 | 875 | ```cpp 876 | struct seg { 877 | vector a; 878 | int l, r; seg *lc, *rc; 879 | void init(int, int); 880 | void update(int x, int y, pii xx) { 881 | if (x > r || y < l) return; 882 | if (x <= l && y >= r) { a.push_back(xx); return; } 883 | lc->update(x, y, xx); 884 | rc->update(x, y, xx); 885 | } 886 | void dfs() { 887 | for (auto i : a) d.join(i.fi, i.se); 888 | if (l == r) ans[l] = now; 889 | if (l != r) { lc->dfs(); rc->dfs(); } 890 | repeat (i, 0, a.size()) d.undo(); 891 | } 892 | } tr[N * 2], *pl; 893 | void seg::init(int _l, int _r) { 894 | l = _l, r = _r; 895 | if (l == r) { return; } 896 | int m = (l + r) >> 1; 897 | lc = ++pl; lc->init(l, m); 898 | rc = ++pl; rc->init(m + 1, r); 899 | } 900 | void init(int l, int r) { 901 | pl = tr; tr->init(l, r); 902 | } 903 | ``` 904 | 905 | ### 2.8. 最大空矩阵 using 悬线法 906 | 907 | - 求01矩阵中全是0的最大连续子矩阵(面积最大)$O(nm)$ 908 | - 此处障碍物是正方形。如果障碍只是一些整点,答案从 $ab$ 变为 $(a+1)(b+1)$ 909 | 910 | ```cpp 911 | int n,m,a[N][N],l[N][N],r[N][N],u[N][N]; 912 | int getlm(){ 913 | int ans=0; 914 | repeat(i,0,n) 915 | repeat(k,0,m) 916 | l[i][k]=r[i][k]=u[i][k]=(a[i][k]==0); 917 | repeat(i,0,n){ 918 | repeat(k,1,m) 919 | if(a[i][k]==0) 920 | l[i][k]=l[i][k-1]+1; // 可以向左延伸几格 921 | repeat_back(k,0,m-1) 922 | if(a[i][k]==0) 923 | r[i][k]=r[i][k+1]+1; // 可以向右延伸几格 924 | repeat(k,0,m) 925 | if(a[i][k]==0){ 926 | if(i!=0 && a[i-1][k]==0){ 927 | u[i][k]=u[i-1][k]+1; // 可以向上延伸几格 928 | l[i][k]=min(l[i][k],l[i-1][k]); 929 | r[i][k]=min(r[i][k],r[i-1][k]); // 如果向上延伸u格,lr对应的修改 930 | } 931 | ans=max(ans,(l[i][k]+r[i][k]-1)*u[i][k]); 932 | } 933 | } 934 | return ans; 935 | } 936 | ``` 937 | 938 | ### 2.9. 搜索 939 | 940 | #### 2.9.1. 舞蹈链 / DLX 941 | 942 | 精确覆盖 943 | 944 | - 在 01 矩阵中找到某些行,它们两两不相交,且它们的并等于全集。 945 | - xy 编号从 1 开始!$O(\exp)$,结点数 $<5000$。 946 | 947 | ```cpp 948 | int n,m; 949 | vector rec; // dance后存所有选中的行的编号 950 | struct DLX{ 951 | #define rep(i,i0,a) for(int i=a[i0];i!=i0;i=a[i]) 952 | int u[N],d[N],l[N],r[N],x[N],y[N]; // N=10010 953 | int sz[N],h[N]; 954 | int top; 955 | void init(){ 956 | top=m; 957 | repeat(i,0,m+1){ 958 | sz[i]=0; u[i]=d[i]=i; 959 | l[i]=i-1; r[i]=i+1; 960 | } 961 | l[0]=m; r[m]=0; 962 | repeat(i,0,n+1)h[i]=-1; 963 | rec.clear(); 964 | } 965 | void add(int x0,int y0){ 966 | top++; sz[y0]++; 967 | x[top]=x0; y[top]=y0; 968 | u[top]=u[y0]; d[top]=y0; 969 | u[d[top]]=d[u[top]]=top; 970 | if(h[x0]<0) 971 | h[x0]=l[top]=r[top]=top; 972 | else{ 973 | l[top]=h[x0]; r[top]=r[h[x0]]; 974 | l[r[h[x0]]]=top; r[h[x0]]=top; 975 | } 976 | } 977 | void remove(int c){ 978 | l[r[c]]=l[c]; r[l[c]]=r[c]; 979 | rep(i,c,d)rep(j,i,r){ 980 | u[d[j]]=u[j]; d[u[j]]=d[j]; 981 | sz[y[j]]--; 982 | } 983 | } 984 | void resume(int c){ 985 | rep(i,c,d)rep(j,i,r){ 986 | u[d[j]]=d[u[j]]=j; 987 | sz[y[j]]++; 988 | } 989 | l[r[c]]=r[l[c]]=c; 990 | } 991 | bool dance(int dep=1){ // 返回是否可行 992 | if(r[0]==0)return 1; 993 | int c=r[0]; 994 | rep(i,0,r)if(sz[c]>sz[i])c=i; 995 | remove(c); 996 | rep(i,c,d){ 997 | rep(j,i,r)remove(y[j]); 998 | if(dance(dep+1)){rec.push_back(x[i]); return 1;} 999 | rep(j,i,l)resume(y[j]); 1000 | } 1001 | resume(c); 1002 | return 0; 1003 | } 1004 | }dlx; 1005 | ``` 1006 | 1007 | - 解数独,一般数独有 $k=9$。 1008 | 1009 | ```cpp 1010 | int k,sqrtk; 1011 | int f(int x,int y,int co){return (x*k+y)*k+co;} 1012 | int ord(char c){return c=='.'?0:isdigit(c)?c-'0':c-'A'+10;} 1013 | char chr(int x){return x<10?x+'0':x-10+'A';} 1014 | int a[16][16]; 1015 | void Solve(){ 1016 | sqrtk=read(); k=sqrtk*sqrtk; 1017 | n=k*k*k,m=k*k*4; dlx.init(); 1018 | repeat(x,0,k){ 1019 | static char str[20]; 1020 | scanf("%s",str); 1021 | repeat(y,0,k){ 1022 | a[x][y]=ord(str[y]); 1023 | int l,r; if(a[x][y])l=r=a[x][y]; else l=1,r=k; 1024 | repeat(co,l,r+1){ 1025 | dlx.add(f(x,y,co),x*k+y+1); 1026 | dlx.add(f(x,y,co),x*k+co+k*k); 1027 | dlx.add(f(x,y,co),y*k+co+k*k*2); 1028 | dlx.add(f(x,y,co),(x/sqrtk*sqrtk+y/sqrtk)*k+co+k*k*3); 1029 | } 1030 | } 1031 | } 1032 | dlx.dance(); 1033 | repeat(i,0,rec.size()){ 1034 | int s=rec[i]-1,x=s/(k*k),y=s/k%k,co=s%k+1; 1035 | a[x][y]=co; 1036 | } 1037 | repeat(i,0,k){ 1038 | repeat(j,0,k)printf("%c",chr(a[i][j])); 1039 | puts(""); 1040 | } 1041 | } 1042 | ``` 1043 | 1044 | 重复覆盖 1045 | 1046 | - 在01矩阵中找到最少的行,它们的并等于全集。 1047 | - xy编号还是从 1 开始!$O(\exp)$,结点数可能 $<3000$。 1048 | 1049 | ```cpp 1050 | struct DLX{ 1051 | #define rep(i,d,s) for(node* i=s->d;i!=s;i=i->d) 1052 | struct node{ 1053 | node *l,*r,*u,*d; 1054 | int x,y; 1055 | }; 1056 | static const int M=2e5; 1057 | node pool[M],*h[M],*R[M],*pl; 1058 | int sz[M],vis[M],ans,clk; 1059 | void init(int n,int m){ // 行和列 1060 | clk=0; ans=inf; pl=pool; ++m; 1061 | repeat(i,0,max(n,m)+1) 1062 | R[i]=sz[i]=0,vis[i]=-1; 1063 | repeat(i,0,m) 1064 | h[i]=new(pl++)node; 1065 | repeat(i,0,m){ 1066 | h[i]->l=h[(i+m-1)%m]; 1067 | h[i]->r=h[(i+1)%m]; 1068 | h[i]->u=h[i]->d=h[i]; 1069 | h[i]->y=i; 1070 | } 1071 | } 1072 | void link(int x,int y){ 1073 | sz[y]++; 1074 | auto p=new(pl++)node; 1075 | p->x=x; p->y=y; 1076 | p->u=h[y]->u; p->d=h[y]; 1077 | p->d->u=p->u->d=p; 1078 | if(!R[x])R[x]=p->l=p->r=p; 1079 | else{ 1080 | p->l=R[x]; p->r=R[x]->r; 1081 | p->l->r=p->r->l=p; 1082 | } 1083 | } 1084 | void remove(node* p){ 1085 | rep(i,d,p)i->l->r=i->r,i->r->l=i->l; 1086 | } 1087 | void resume(node* p){ 1088 | rep(i,u,p)i->l->r=i->r->l=i; 1089 | } 1090 | int eval(){ 1091 | ++clk; int ret=0; 1092 | rep(i,r,h[0]) 1093 | if(vis[i->y]!=clk){ 1094 | ++ret; 1095 | vis[i->y]=clk; 1096 | rep(j,d,i)rep(k,r,j)vis[k->y]=clk; 1097 | } 1098 | return ret; 1099 | } 1100 | void dfs(int d){ 1101 | if(h[0]->r==h[0]){ans=min(ans,d); return;} 1102 | if(eval()+d>=ans)return; 1103 | node* c; int m=inf; 1104 | rep(i,r,h[0]) 1105 | if(sz[i->y]y]; c=i;} 1106 | rep(i,d,c){ 1107 | remove(i); rep(j,r,i)remove(j); 1108 | dfs(d+1); 1109 | rep(j,l,i)resume(j); resume(i); 1110 | } 1111 | } 1112 | int solve(){ // 返回最优解 1113 | ans=inf; dfs(0); return ans; 1114 | } 1115 | }dlx; 1116 | ``` 1117 | 1118 | #### 2.9.2. 启发式算法 1119 | 1120 | A-star 1121 | 1122 | - 定义 $g(v)$ 是 s 到 v 的实际代价,$h(v)$ 是 v 到 t 的估计代价。 1123 | - 定义估价函数 $f(v)=g(v)+h(v)$。 1124 | - 每次从堆里取出 $f(v)$ 最小的点进行更新。 1125 | - 如果满足 $h(v_1)+w(v_2,v_1)\ge h(v_2)$ (存疑)则不需要重复更新同一点,可以用set标记,已标记的不入堆。 1126 | 1127 | 模拟退火 1128 | 1129 | - 以当前状态 X 为中心,半径为温度 T 的圆(或球)内选一个新状态 Y。 1130 | - 计算 $D=E(Y)-E(X)$ 新状态势能减去当前状态势能。 1131 | - 如果 $D<0$ 则状态转移(势能 E 越小越优)。 1132 | - 否则状态转移的概率是 $\exp(-\dfrac{KD}{T})$(Metropolis接受准则,~~学不会~~)。 1133 | - 最后温度乘以降温系数,返回第一步。 1134 | - 需要调 3 个参数:初始温度,终止温度,降温系数(?)。 1135 | - 注意点: 1136 | - 让运行时间在TLE边缘试探。 1137 | - 多跑几次退火。 1138 | - 多交几次(注意风险)。 1139 | - 可以先不用某准则,输出中间过程后再调参。 1140 | 1141 | ```cpp 1142 | lf rndf(){return rnd()*1.0/rnd.max();} 1143 | vec rndvec(){return vec(rndf()*2-1,rndf()*2-1);} 1144 | // lf E(vec); // 计算势能 1145 | struct state{ // 表示一个状态 1146 | vec v; lf e; // 位置和势能 1147 | state(vec v=vec()):v(v),e(E(v)){} 1148 | operator lf(){return e;} 1149 | }; 1150 | state getstate(){ 1151 | state X; lf T=1000; 1152 | auto work=[&](){ 1153 | state Y=X.v+rndvec()*T; 1154 | if(Y1e-9){ 1158 | if(work()){work(); work(); T*=1.1;} 1159 | T*=0.99992; 1160 | } 1161 | return X; 1162 | } 1163 | 1164 | void solve(){ 1165 | state X; 1166 | repeat(i,0,6){ 1167 | state Y=getstate(); 1168 | if(X>Y)X=Y; 1169 | } 1170 | printf("%.10f\n",lf(X)); 1171 | } 1172 | ``` 1173 | 1174 | ### 2.10. 动态规划 1175 | 1176 | #### 2.10.1. 多重背包 1177 | 1178 | - 二进制版,$O(nV\log V)$,V 是总容量。 1179 | 1180 | ```cpp 1181 | int n,V; ll dp[N]; 1182 | void push(int val,int v,int c){ // 处理物品(价值=val,体积=v,个数=c) 1183 | for(int b=1;c;c-=b,b=min(b*2,c)){ 1184 | ll dv=b*v,dval=b*val; 1185 | repeat_back(j,dv,V+1) 1186 | dp[j]=max(dp[j],dp[j-dv]+dval); 1187 | } 1188 | } 1189 | // 初始化fill(dp,dp+V+1,0),结果是dp[V] 1190 | ``` 1191 | 1192 | - 单调队列版,$O(nV)$,V 是总容量。 1193 | 1194 | ```cpp 1195 | int n,V; ll dp[N]; 1196 | void push(int val,int v,int c){ // 处理物品(价值=val,体积=v,个数=c) 1197 | static deque< pair > q; // 单调队列,fi是位置,se是价值 1198 | if(v==0){ 1199 | repeat(i,0,V+1)dp[i]+=val*c; 1200 | return; 1201 | } 1202 | c=min(c,V/v); 1203 | repeat(d,0,v){ 1204 | q.clear(); 1205 | repeat(j,0,(V-d)/v+1){ 1206 | ll t=dp[d+j*v]-j*val; 1207 | while(!q.empty() && t>=q.back().se) 1208 | q.pop_back(); 1209 | q.push_back({j,t}); 1210 | while(q.front().fi &mp,vector &push,vector &pop){ // pii = 1235 | for(auto i:push){ 1236 | int key=i.fi,val=i.se; 1237 | auto r=mp.lower_bound(key); 1238 | if(r->fi==key) 1239 | r->se+=val,++r; 1240 | else 1241 | mp.emplace_hint(r,key,val); 1242 | auto s=r; int sum=0; 1243 | while(sum+s->se<=val){ 1244 | pop.push_back(*s); 1245 | sum+=s->se; 1246 | ++s; 1247 | } 1248 | if(s->fi!=INF)pop.push_back({s->fi,val-sum}); 1249 | s->se-=val-sum; 1250 | mp.erase(r,s); 1251 | } 1252 | push.clear(); 1253 | } 1254 | // init: mp.clear(),mp[INF]=INF; 1255 | // query: INF-mp[i].rbegin()->se 1256 | ``` 1257 | 1258 | #### 2.10.3. 数位 dp 1259 | 1260 | - 记忆化搜索,DFS的参数lim表示是否被限制,lz表示当前位的前一位是不是前导零。 1261 | - 复杂度等于状态数。 1262 | - 如果每个方案贡献不是1,dp可能要变成struct数组(cnt,sum,....)。 1263 | 1264 | ```cpp 1265 | ll dp[20][*][2],bit[20]; // 这个[2]表示lz状态,如果lz被使用了的话就需要记录 1266 | ll dfs(int pos,ll *,bool lim=1,bool lz=1){ 1267 | if(pos==-1)return *; // 返回该状态是否符合要求(0或1) 1268 | ll &x=dp[pos][*]; 1269 | if(!lim && x!=-1)return x; 1270 | ll ans=0; 1271 | int maxi=lim?bit[pos]:9; 1272 | repeat(i,0,maxi+1){ 1273 | ...// 状态转移 1274 | if(lz && i==0)...// 可能要用lz,其他地方都不用 1275 | ans+=dfs(pos-1,*, 1276 | lim && i==maxi, 1277 | lz && i==0); 1278 | } 1279 | if(!lim)x=ans; // 不限制的时候才做存储 1280 | return ans; 1281 | } 1282 | ll solve(ll n){ 1283 | int len=0; 1284 | while(n)bit[len++]=n%10,n/=10; 1285 | return dfs(len-1,*); 1286 | } 1287 | signed main(){ 1288 | mst(dp,-1); // 在很多时候dp值可以反复使用 1289 | ll t=read(); 1290 | while(t--){ 1291 | ll l=read(),r=read(); 1292 | printf("%lld\n",solve(r)-solve(l-1)); 1293 | } 1294 | return 0; 1295 | } 1296 | ``` 1297 | 1298 | #### 2.10.4. 换根 dp 1299 | 1300 | - 两次 DFS。 1301 | - 第一次求所有点所在子树的答案 $dp_v$,此时 $dp_{rt}$ 是 $rt$ 的最终答案。 1302 | - 第二次将根转移来算其他点的最终答案,回溯时复原即可。 1303 | 1304 | ```cpp 1305 | void dfs1(int x,int fa=-1){ 1306 | for(auto p:a[x]) 1307 | if(p!=fa){ 1308 | dfs1(p,x); 1309 | dp[x]+=op(dp[p]); 1310 | } 1311 | } 1312 | void dfs2(int x,int fa=-1){ 1313 | ans[x]=dp[x]; 1314 | for(auto p:a[x]) 1315 | if(p!=fa){ 1316 | dp[x]-=op(dp[p]); 1317 | dp[p]+=op(dp[x]); 1318 | dfs2(p,x); 1319 | dp[p]-=op(dp[x]); 1320 | dp[x]+=op(dp[p]); 1321 | } 1322 | } 1323 | ``` 1324 | 1325 | #### 2.10.5. 斜率优化 1326 | 1327 | - 例:HDOJ3507 1328 | - $dp_i=\min\limits_{j=0}^{i-1}[dp_j+(s_i-s_j)^2+M]$ 1329 | - 考虑 $ks_i$ 的线段的左端点,作为决策点 1331 | 1332 | #### 2.10.6. 四边形优化 1333 | 1334 | - $dp(l,r)=\min\limits_{k=l}^{r-1}[dp(l,k)+dp(k+1,r)]+w(l,r)$ 1335 | - 其中 $w(l,r)$ 满足 1336 | - 区间包含单调性:任意 $l \le l' \le r' \le r$ 有 $w(l',r')\le w(l,r)$ 1337 | - 四边形不等式:任意 $a \le b \le c \le d$ 有 $w(a,c)+w(b,d)\le w(a,d)+w(b,c)$(若等号恒成立则满足四边形恒等式) 1338 | - 决策单调性:令 $m(l,r)$ 为最优决策点(满足 $dp(l,r)=dp(l,m)+dp(m+1,r)+w(l,r)$),则有 $m(l,r-1) \le m(l,r) \le m(l+1,r)$,遍历这个区间可以优化至 $O(n^2)$ 1339 | 1340 | ```cpp 1341 | repeat(i,0,n)dp[i][i]=0,m[i][i]=i; 1342 | repeat(len,2,n+1) 1343 | for(int l=0,r=len-1;rdp[l][k]+dp[k+1][r]+w(l,r)){ 1347 | dp[l][r]=dp[l][k]+dp[k+1][r]+w(l,r); 1348 | m[l][r]=k; 1349 | } 1350 | } 1351 | ``` 1352 | 1353 | - $dp(i)=\min\limits_{k=1}^{i-1}w(k,i)$,$w(l,r)$ 满足四边形不等式 1354 | - 决策单调性:令 $m_i$ 为最优决策点(满足 $dp(i)=w(m,i)$),则 $m_{i-1}\le m_i$,因此可以分治优化成 $O(n\log n)$ 1355 | 1356 | ### 2.11. 哈希 / Hash 1357 | 1358 | #### 2.11.1. 字符串哈希 1359 | 1360 | - 如果不需要区间信息,可以调用 `hash()(s)` 获得 ull 范围的 hash 值。 1361 | - 碰撞概率:单哈希 $10^6$ 次比较大约有 $\dfrac 1 {1000}$ 概率碰撞。 1362 | - 更新生日悖论:n 个字符串,两个串 Hash 值相同概率 $P\approx 1-(1-\tfrac{1}{mod})^{\tfrac{n(n-1)}{2}}$。 1363 | - 支持查询子串 hash 值,编号从 0 开始,初始化 $O(n)$,子串查询 $O(1)$。 1364 | 1365 | ```cpp 1366 | template 1367 | struct Hash{ 1368 | template 1369 | struct hs{ 1370 | vector a,p; 1371 | hs(const string &s=""){ 1372 | a={0},p={1}; 1373 | for(auto c:s){ 1374 | a.push_back((1ll*a.back()*b+(c^x))%mod); 1375 | p.push_back(1ll*p.back()*b%mod); 1376 | } 1377 | } 1378 | ll q(int l,int r){ 1379 | return (a[r+1]-1ll*a[l]*p[r-l+1]%mod+mod)%mod; 1380 | } 1381 | ll q2(int l,int r){ // 循环字符串 1382 | if(l<=r)return q(l,r); 1383 | return (a[r+1]+q(l,a.size()-2)*p[r+1])%mod; 1384 | } 1385 | }; 1386 | hs<257,1000000007> h1; 1387 | hs<257,2147483647> h2; 1388 | Hash(const string &s):h1(s),h2(s){} 1389 | pii query(int l,int r){ 1390 | return {h1.q(l,r),h2.q(l,r)}; 1391 | } 1392 | pii query2(int l,int r){ // 循环字符串 1393 | return {h1.q2(l,r),h2.q2(l,r)}; 1394 | } 1395 | }; 1396 | ``` 1397 | 1398 | #### 2.11.2. 质因数哈希 1399 | 1400 | ```cpp 1401 | int fac(int n,int c,int mod,const function &f){ 1402 | int p=c*c%mod,ans=0; 1403 | for(int i=2;i*i<=n;i++){ 1404 | int cnt=0; 1405 | while(n%i==0)n/=i,cnt++; 1406 | ans=(ans+p*f(cnt))%mod; 1407 | p=p*c%mod; 1408 | } 1409 | if(n>1)ans=(ans+qpow(c,n,mod)*f(1))%mod; 1410 | return ans; 1411 | } 1412 | // 例:匹配乘积为x^k(x任意)的两个数 1413 | pii hash1(int n){ 1414 | return pii( 1415 | fac(n,101,2147483647,[](int x){return x%k;}), 1416 | fac(n,103,1000000007,[](int x){return x%k;}) 1417 | ); 1418 | } 1419 | pii hash2(int n){ 1420 | return pii( 1421 | fac(n,101,2147483647,[](int x){return (k-x%k)%k;}), 1422 | fac(n,103,1000000007,[](int x){return (k-x%k)%k;}) 1423 | ); 1424 | } 1425 | ``` 1426 | 1427 | ## 3. 字符串 1428 | 1429 | - (~~我字符串是最菜的~~) 1430 | - 寻找模式串p在文本串t中的所有出现 1431 | 1432 | ### 3.1. 字符串函数 1433 | 1434 | #### 3.1.1. 前缀函数 using kmp 1435 | 1436 | - $p[x]$ 表示满足 `s.substr(0,k)==s.substr(x-k,k)` 且 $x\not=k$ 的 k 的最大值,$p[0]=0$ 1437 | - 线性复杂度 1438 | 1439 | ```cpp 1440 | int p[N]; 1441 | void kmp(const string &s) { 1442 | p[0] = 0; int k = 0; 1443 | repeat (i, 1, s.length()){ 1444 | while (k > 0 && s[i] != s[k]) k = p[k - 1]; 1445 | if (s[i] == s[k]) k++; 1446 | p[i] = k; 1447 | } 1448 | } 1449 | void solve(string s1,string s2){ // 模拟 s1.find(s2) 1450 | kmp(s2+'#'+s1); 1451 | repeat(i,s2.size()+1,s.size()) 1452 | if(p[i]==(int)s2.size()) 1453 | ans.push_back(i-2*s2.size()); // 编号从 0 开始的左端点 1454 | } 1455 | ``` 1456 | 1457 | ```cpp 1458 | struct KMP{ 1459 | string s; 1460 | vector p; 1461 | int get(int k,char c){ 1462 | while(k>0 && c!=s[k])k=p[k-1]; 1463 | if(c==s[k])k++; 1464 | return k; 1465 | } 1466 | void init(const string &_s){ 1467 | p.assign(1,0); int k=0; s=_s+'#'; 1468 | repeat(i,1,s.size())p.push_back(k=get(k,s[i])); 1469 | } 1470 | int size(){return s.size()-1;} 1471 | }; 1472 | ``` 1473 | 1474 | #### 3.1.2. z 函数 using exkmp 1475 | 1476 | - $z[x]$ 表示满足 `s.substr(0,k)==s.substr(x,k)` 的 k 的最大值,$z[0]=0$ 1477 | - 线性复杂度 1478 | 1479 | ```cpp 1480 | int z[N]; 1481 | void exkmp(const string &s){ // 求s的z函数 1482 | fill(z,z+s.size(),0); int l=0,r=0; 1483 | repeat(i,1,s.size()){ 1484 | if(i<=r)z[i]=min(r-i+1,z[i-l]); 1485 | while(i+z[i]<(int)s.size() && s[z[i]]==s[i+z[i]])z[i]++; 1486 | if(i+z[i]-1>r)l=i,r=i+z[i]-1; 1487 | } 1488 | } 1489 | ``` 1490 | 1491 | #### 3.1.3. 马拉车 / Manacher 1492 | 1493 | - 预处理为 `"#*A*A*A*A*A*"` 1494 | - 线性复杂度 1495 | 1496 | ```cpp 1497 | int len[N*2]; char s[N*2]; // 两倍内存 1498 | int manacher(char s1[]){ // s1可以是s 1499 | int n=strlen(s1)*2+1; 1500 | repeat_back(i,0,n)s[i+1]=(i%2==0?'*':s1[i/2]); 1501 | n++; s[0]='#'; s[n++]=0; 1502 | len[0]=0; 1503 | int mx=0,id=0,ans=0; 1504 | repeat(i,1,n-1){ 1505 | if(imx)mx=len[i]+i,id=i; 1509 | ans=max(ans,len[i]-1); // 最长回文串长度 1510 | } 1511 | return ans; 1512 | } 1513 | ``` 1514 | 1515 | #### 3.1.4. 最小表示法 1516 | 1517 | - 求 s 重复无数次的字符串最小后缀的左端点 1518 | - 线性复杂度 1519 | 1520 | ```cpp 1521 | int minstr(const string &s){ 1522 | int k=0,i=0,j=1,n=s.size(); 1523 | while(max(k,max(i,j))s[(j+k)%n]?i+=k+1:j+=k+1; 1527 | if(i==j)i++; 1528 | k=0; 1529 | } 1530 | } 1531 | return min(i,j); 1532 | } 1533 | ``` 1534 | 1535 | #### 3.1.5. 后缀数组 / SA 1536 | 1537 | - $sa[i]$ 表示所有后缀中第 i 小的后缀是 `s.substr(sa[i],-1)` 1538 | - $rk[i]$ 表示所有后缀中 `s.substr(i,-1)` 是第 $rk[i]$ 小 1539 | - 编号从 1 开始!$O(n\log n)$ 1540 | 1541 | ```cpp 1542 | int sa[N],rk[N]; // sa,rk即结果 1543 | void get_sa(const string &S){ 1544 | static int pre[N*2],id[N],px[N],cnt[N]; 1545 | int n=S.length(),m=256; 1546 | const char *const s=S.c_str()-1; // 为了编号从1开始 1547 | for(int i=1;i<=n;i++)cnt[rk[i]=s[i]]++; 1548 | for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1]; 1549 | for(int i=n;i>=1;i--)sa[cnt[rk[i]]--]=i; 1550 | for(int w=1;wn-w;i--)id[++t]=i; 1553 | for(int i=1;i<=n;i++) 1554 | if(sa[i]>w)id[++t]=sa[i]-w; 1555 | mst(cnt,0); 1556 | for(int i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++; 1557 | for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1]; 1558 | for(int i=n;i>=1;i--)sa[cnt[px[i]]--]=id[i]; 1559 | memcpy(pre,rk,sizeof(rk)); 1560 | int p=0; 1561 | static auto pp=[&](int x){return pii(pre[x],pre[x+w]);}; 1562 | for(int i=1;i<=n;i++) 1563 | rk[sa[i]]=pp(sa[i])==pp(sa[i-1])?p:++p; 1564 | m=p; // 优化计数排序值域 1565 | } 1566 | } 1567 | ``` 1568 | 1569 | - sa可以在同一文本串中在线多次查找模式串(二分查找) 1570 | 1571 | #### 3.1.6. height数组 1572 | 1573 | - 定义 $lcp(i,j)=$ 后缀 i 和后缀 j 的最长公共前缀长度 1574 | - 定义 $height[i]=lcp(sa[i],sa[i-1])$,$height[1]=0$ 1575 | - $height[rk[i]]\ge height[rk[i-1]]-1$ 1576 | - 编号从 1 开始,$O(n)$ 1577 | 1578 | ```cpp 1579 | for(int i=1,k=0;i<=n;i++){ 1580 | if(k)k--; 1581 | while(s[i+k]==s[sa[rk[i]-1]+k])k++; 1582 | ht[rk[i]]=k; 1583 | } 1584 | ``` 1585 | 1586 | - 不相等的子串个数为 $\dfrac{n(n+1)}{2}-\sum\limits_{i=2}^{n}height[i]$ 1587 | 1588 | ### 3.2. 自动机 1589 | 1590 | #### 3.2.1. 字典树 / Trie 1591 | 1592 | - 线性复杂度 1593 | 1594 | ```cpp 1595 | struct trie{ 1596 | struct node{ 1597 | int to[26],cnt; 1598 | int &operator[](int n){return to[n];} 1599 | }a[N]; 1600 | int t; 1601 | void init(){t=0; a[t++]=node();} 1602 | void insert(const char s[]){ 1603 | int k=0; 1604 | for(int i=0;s[i];i++){ 1605 | int c=s[i]-'a'; // small letter 1606 | if(!a[k][c])a[k][c]=t,a[t++]=node(); 1607 | k=a[k][c]; 1608 | // a[k].son++; // 子树大小 1609 | } 1610 | a[k].cnt++; 1611 | } 1612 | int query(const char s[]){ 1613 | int k=0; 1614 | for(int i=0;s[i];i++){ 1615 | int c=s[i]-'a'; // small letter 1616 | if(!a[k][c])return 0; 1617 | k=a[k][c]; 1618 | } 1619 | return a[k].cnt; 1620 | } 1621 | }t; 1622 | ``` 1623 | 1624 | - 最小异或生成树,01字典树 1625 | - 归并,$O(n\log^2n)$ 1626 | 1627 | ```cpp 1628 | struct trie{ 1629 | static const int B=30; 1630 | struct node{ 1631 | int to[2]; 1632 | int &operator[](int n){return to[n];} 1633 | }a[N]; 1634 | int t; 1635 | void init(){t=0; a[t++]=node();} 1636 | void insert(ll s){ 1637 | int k=0; 1638 | repeat_back(i,0,B){ 1639 | int c=(s>>i)&1; 1640 | if(!a[k][c])a[k][c]=t,a[t++]=node(); 1641 | k=a[k][c]; 1642 | } 1643 | } 1644 | ll query(ll s){ // the min value in {s^t | t in trie} 1645 | int k=0; ll ans=0; 1646 | repeat_back(i,0,B){ 1647 | int c=(s>>i)&1; 1648 | if(!a[k][c])c^=1,ans^=1ll<=r-1)return; 1657 | int m=lower_bound(a+l,a+r,s)-a; 1658 | if(l>1); merge(m,r,s>>1); 1666 | } 1667 | void solve(){ 1668 | sort(a,a+n); 1669 | merge(0,n,1ll<<31); 1670 | } 1671 | ``` 1672 | 1673 | - 可持久化字典树,查询与 s 异或后的序列的区间最大值 1674 | 1675 | ```cpp 1676 | struct trie{ 1677 | static const int B=30; 1678 | struct node{ 1679 | int to[2]; int lst; 1680 | int &operator[](int n){return to[n];} 1681 | }a[N]; 1682 | int t; 1683 | int clone(int k){a[t]=a[k]; return t++;} 1684 | int init(){t=1; return ins(0,0,0);} 1685 | int ins(int rt,ll s,int lst){ 1686 | int k=rt=clone(rt); 1687 | repeat_back(i,0,B){ 1688 | int c=(s>>i)&1; 1689 | a[k][c]=clone(a[k][c]); 1690 | k=a[k][c]; 1691 | a[k].lst=max(a[k].lst,lst); 1692 | } 1693 | return rt; 1694 | } 1695 | ll q(int rt,ll s,int lst){ // the max value in {s^t | t in trie} 1696 | int k=rt; ll ans=0; 1697 | repeat_back(i,0,B){ 1698 | int c=(s>>i)&1; c^=1,ans^=1ll< p; // 指向模式串集合 1729 | }a[N]; 1730 | int t; 1731 | vector q; // 存了BFS序 1732 | void init(){t=0; a[t++]=node(); q.clear();} 1733 | void insert(const char s[]/*,int ptr*/){ // 将模式串插入字典树 1734 | int k=0; 1735 | for(int i=0;s[i];i++){ 1736 | int c=s[i]-c0; 1737 | if(!a[k][c])a[k][c]=t,a[t++]=node(); 1738 | k=a[k][c]; 1739 | } 1740 | a[k].trie_cnt++; 1741 | // a[k].p.push_back(ptr); 1742 | } 1743 | void build(){ // 构建fail树,将字典树扩展为图 1744 | int tail=0; 1745 | repeat(i,0,sigma) 1746 | if(a[0][i]) 1747 | q.push_back(a[0][i]); 1748 | while(tail!=(int)q.size()){ 1749 | int k=q[tail++]; 1750 | repeat(i,0,sigma) 1751 | if(a[k][i]) 1752 | a[a[k][i]].fail=a[a[k].fail][i],q.push_back(a[k][i]); 1753 | else 1754 | a[k][i]=a[a[k].fail][i]; 1755 | } 1756 | } 1757 | void query(const char s[]){ // 记录文本串中模式串出现次数 1758 | int k=0; 1759 | for(int i=0;s[i];i++){ 1760 | int c=s[i]-c0; 1761 | k=a[k][c]; 1762 | a[k].cnt++; // fail树上差分 1763 | // for(int kk=k;kk!=0;kk=a[kk].fail) 1764 | // for(auto p:a[kk].p) 1765 | // ans[p]++; // 代替下面的反向遍历,我也不知道什么时候用 1766 | } 1767 | repeat_back(i,0,q.size()){ // 反向遍历BFS序 1768 | a[a[q[i]].fail].cnt+=a[q[i]].cnt; // 差分求和 1769 | // for(auto p:a[q[i]].p)ans[p]=a[q[i]].cnt; // 反馈答案 1770 | } 1771 | } 1772 | }ac; 1773 | ``` 1774 | 1775 | #### 3.2.3. 后缀自动机 / SAM 1776 | 1777 | - 给定字符串中所有子串对应了SAM中从源点出发的一条路径 1778 | - SAM是一个DAG,至多有 $2n-1$ 个结点和 $3n-4$ 条边 1779 | - 每个结点表示一个endpos等价类,对应子串长度区间 $[a[a[i].fa].len+1,a[i].len]$ 1780 | - 构造 $O(n)$,编号从 1 开始($a[1]$ 表示源点) 1781 | 1782 | ```cpp 1783 | struct SAM{ 1784 | static const int sigma=26,c0='a'; // 小写字母 1785 | struct node{ 1786 | int to[sigma],fa,len; 1787 | int &operator[](int x){return to[x];} 1788 | }a[N*2]; 1789 | int last,tot; 1790 | void init(){last=tot=1;} 1791 | int add(){a[++tot]=node(); return tot;} 1792 | void push(int c){ 1793 | c-=c0; 1794 | int x=last,nx=last=add(); 1795 | a[nx].len=a[x].len+1; 1796 | for(;x && a[x][c]==0;x=a[x].fa)a[x][c]=nx; 1797 | if(x==0)a[nx].fa=1; 1798 | else{ 1799 | int p=a[x][c]; 1800 | if(a[p].len==a[x].len+1)a[nx].fa=p; 1801 | else{ 1802 | int np=add(); 1803 | a[np]=a[p]; a[np].len=a[x].len+1; 1804 | a[p].fa=a[nx].fa=np; 1805 | for(;x && a[x][c]==p;x=a[x].fa)a[x][c]=np; 1806 | } 1807 | } 1808 | } 1809 | }sam; 1810 | // 构造:for(auto i:s)sam.push(i); 1811 | ``` 1812 | 1813 | ## 4. 技巧 1814 | 1815 | ### 4.1. 位运算 1816 | 1817 | #### 4.1.1. 位运算操作 1818 | 1819 | - 中点向下取整 `(x+y)/2: (x & y) + ((x ^ y) >> 1)` 1820 | - 中点向上取整 `(x+y+1)/2: (x | y) - ((x ^ y) >> 1)` 1821 | - 一般来说用 `x + (y - x >> 1)` 1822 | - `abs(n): (n ^ (n >> 31)) - (n >> 31)` 1823 | - `max(a,b): b & ((a - b) >> 31) | a & (~(a - b) >> 31)` 1824 | - `min(a,b): a & ((a - b) >> 31) | b & (~(a - b) >> 31)` 1825 | 1826 | ```cpp 1827 | template T getbit(T x,int pos){return x>>pos&1;} 1828 | template void setbit(T &x,int pos,bool z){z?x|=T(1)<>1)|y; // 这里有一个位反~ 1858 | } 1859 | ``` 1860 | 1861 | ### 4.2. 浮点数 1862 | 1863 | 浮点数操作 1864 | 1865 | ```cpp 1866 | const lf eps=1e-11; 1867 | if(abs(x) 1912 | void write(T x){ // slow 1913 | if(x<0)x=-x,putchar('-'); 1914 | if(x>=10)write(x/10); 1915 | putchar(x%10^48); 1916 | } 1917 | ``` 1918 | 1919 | ```cpp 1920 | char getc(){ // 代替 getchar,用了这个就不能用其他读入函数如 scanf 1921 | static char now[1<<21],*S,*T; 1922 | if(T==S){T=(S=now)+fread(now,1,1<<21,stdin); if(T==S)return EOF;} 1923 | return *S++; 1924 | } 1925 | ``` 1926 | 1927 | #### 4.3.3. STL 手写内存分配器 1928 | 1929 | ```cpp 1930 | static char space[10000000],*sp=space; 1931 | template 1932 | struct allc:allocator{ 1933 | allc(){} 1934 | template 1935 | allc(const allc &a){} 1936 | template 1937 | allc& operator=(const allc &a){return *this;} 1938 | template 1939 | struct rebind{typedef allc other;}; 1940 | T* allocate(size_t n){ 1941 | T *res=(T*)sp; 1942 | sp+=n*sizeof(T); 1943 | return res; 1944 | } 1945 | void deallocate(T* p,size_t n){} 1946 | }; 1947 | vector< int,allc > a; 1948 | ``` 1949 | 1950 | #### 4.3.4. 取模卡常 1951 | 1952 | 大量对同一个数取模的优化。(其实根本用不到) 1953 | 1954 | ```cpp 1955 | struct fastmod { 1956 | using u64 = uint64_t; 1957 | using u128 = __uint128_t; 1958 | int f, l; u64 m, d; 1959 | fastmod(u64 d): d(d) { 1960 | l = 64 - __builtin_clzll(d - 1); 1961 | const u128 one = 1; 1962 | u128 M = ((one << (64 + l)) + (one << l)) / d; 1963 | if(M < (one << 64)) f = 1, m = M; 1964 | else f = 0, m = M - (one << 64); 1965 | } 1966 | friend u64 operator/(u64 n, const fastmod &m) { // get n / d 1967 | if (m.f) return u128(n) * m.m >> 64 >> m.l; 1968 | else { 1969 | u64 t = u128(n) * m.m >> 64; 1970 | return (((n - t) >> 1) + t) >> (m.l - 1); 1971 | } 1972 | } 1973 | friend u64 operator%(u64 n, const fastmod &m) { // get n % d 1974 | return n - n / m * m.d; 1975 | } 1976 | }; 1977 | ``` 1978 | 1979 | #### 4.3.5. 其他优化 1980 | 1981 | ```cpp 1982 | int mul(int a,int b,int m=mod){ // 汇编,a * b % mod 1983 | int ret; 1984 | __asm__ __volatile__ ( 1985 | "\tmull %%ebx\n\tdivl %%ecx\n" 1986 | : "=d"(ret) 1987 | : "a"(a),"b"(b),"c"(m) 1988 | ); 1989 | return ret; 1990 | } 1991 | int bitrev(int x){ // 反转二进制 x 1992 | int z; 1993 | #define op(i) \ 1994 | z = ~0u / (1 << i | 1); \ 1995 | x = (x & z) << i | (x >> i & z) 1996 | op(1); op(2); op(4); op(8); op(16); 1997 | #undef op 1998 | return x; 1999 | } 2000 | int kthbit(int x,int k){ // 从低到高第 k 个 1 的位置 2001 | int ans=0,z; 2002 | #define op(b) \ 2003 | z=__builtin_popcount(x & ((1 << b) - 1)); \ 2004 | if(z>=b; 2005 | op(16); op(8); op(4); op(2); op(1); 2006 | #undef op 2007 | return ans; 2008 | } 2009 | ``` 2010 | 2011 | 查表法反转二进制位 2012 | 2013 | ```cpp 2014 | u32 rev_tbl[65537]; 2015 | void bitrevinit() { 2016 | rev_tbl[0] = 0; 2017 | for (int i=1; i<65536; ++i) 2018 | rev_tbl[i] = (rev_tbl[i>>1]>>1) | ((i&1)<<15); 2019 | } 2020 | u32 bitrev(u32 x) { 2021 | return (rev_tbl[x & 65535] << 16) | rev_tbl[x >> 16]; 2022 | } 2023 | ``` 2024 | 2025 | ```text 2026 | // (都是听说的) 2027 | 1ll*a 比 (ll)a 快 2028 | 取模:x%mod 优化为 x 2063 | using namespace std; 2064 | int main(){ 2065 | for(int i=0;;i++){ 2066 | if(i%10==0)cerr< test.in"); 2068 | system("test1.exe < test.in > a.out"); 2069 | system("test2.exe < test.in > b.out"); 2070 | if(system("fc a.out b.out")){ // linux: diff 2071 | system("pause"); 2072 | return 0; 2073 | } 2074 | } 2075 | } 2076 | ``` 2077 | 2078 | 备选 2079 | 2080 | ```cpp 2081 | #include 2082 | using namespace std; 2083 | ifstream a,b; 2084 | int main(){ 2085 | for(int i=0;;i++){ 2086 | if(i%10==0)cerr< data.txt"); 2088 | system("A.exe < data.txt > a.out"); 2089 | system("B.exe < data.txt > b.out"); 2090 | a.open("a.out"); 2091 | b.open("b.out"); 2092 | while(a.good() || b.good()){ 2093 | if(a.get()!=b.get()){ 2094 | system("pause"); 2095 | return 0; 2096 | } 2097 | } 2098 | a.close(),b.close(); 2099 | } 2100 | } 2101 | ``` 2102 | 2103 | ### 4.7. 战术分析 2104 | 2105 | 交题前~~给爷~~好好考虑: 2106 | 2107 | - 数据范围。 2108 | - 初始化,并测试一下。 2109 | - 考虑极端数据!! 2110 | - 试一试 `ctrl+Z / ctrl+D` 能否结束。 2111 | 2112 | 坑点 2113 | 2114 | - `ll t;` `1<=mod?x-mod:x;}`(ll 改 int 以及两个分支改一个),减法改成 `D(x-y+mod)`;mod 等等也改成 int 类型。 2124 | - 临时变量一定要初始化,特别是 struct。 2125 | - 滑动窗口可能要写 `r = max(l, r);` 强制区间为合法区间 2126 | -------------------------------------------------------------------------------- /Graph.md: -------------------------------------------------------------------------------- 1 | # 图论 2 | 3 | - [1. 图论基础](#1-图论基础) 4 | - [1.1. 邻接表](#11-邻接表) 5 | - [1.2. 前向星](#12-前向星) 6 | - [1.3. 拓扑排序 / Toposort](#13-拓扑排序--toposort) 7 | - [2. 线段树优化建图](#2-线段树优化建图) 8 | - [3. 最短路径](#3-最短路径) 9 | - [3.1. 单源正权 using Dijkstra](#31-单源正权-using-dijkstra) 10 | - [3.2. 多源 using Floyd](#32-多源-using-floyd) 11 | - [3.3. 一般单源 using SPFA](#33-一般单源-using-spfa) 12 | - [3.4. 多源 using Johnson](#34-多源-using-johnson) 13 | - [3.5. n2 Dijkstra](#35-n2-dijkstra) 14 | - [4. 最小生成树 / MST](#4-最小生成树--mst) 15 | - [4.1. Kruskal](#41-kruskal) 16 | - [4.2. Boruvka](#42-boruvka) 17 | - [4.3. n2 Prim](#43-n2-prim) 18 | - [5. 树论](#5-树论) 19 | - [5.1. 树的直径](#51-树的直径) 20 | - [5.2. 树的重心](#52-树的重心) 21 | - [5.3. 最近公共祖先 / LCA](#53-最近公共祖先--lca) 22 | - [5.3.1. 树上倍增解法](#531-树上倍增解法) 23 | - [5.3.2. 欧拉序列建 ST 表解法](#532-欧拉序列建-st-表解法) 24 | - [5.3.3. 树链剖分解法](#533-树链剖分解法) 25 | - [5.3.4. Tarjan 解法](#534-tarjan-解法) 26 | - [5.3.5. 一些关于lca的问题](#535-一些关于lca的问题) 27 | - [5.4. 树链剖分](#54-树链剖分) 28 | - [5.5. 树分治](#55-树分治) 29 | - [5.5.1. 点分治](#551-点分治) 30 | - [5.6. 虚树](#56-虚树) 31 | - [5.7. 树上启发式合并](#57-树上启发式合并) 32 | - [6. 联通性相关](#6-联通性相关) 33 | - [6.1. 强联通分量 SCC + 缩点](#61-强联通分量-scc--缩点) 34 | - [6.2. 边双连通分量 using Tarjan](#62-边双连通分量-using-tarjan) 35 | - [6.3. 割点 / 割顶](#63-割点--割顶) 36 | - [7. 2-Sat 问题](#7-2-sat-问题) 37 | - [8. 支配树 using Lengauer-Tarjan 算法](#8-支配树-using-lengauer-tarjan-算法) 38 | - [9. 图上的NP问题](#9-图上的np问题) 39 | - [9.1. 最大团 and 极大团计数](#91-最大团-and-极大团计数) 40 | - [9.2. 最小染色数](#92-最小染色数) 41 | - [10. 仙人掌 using 圆方树](#10-仙人掌-using-圆方树) 42 | - [11. 二分图](#11-二分图) 43 | - [11.1. 二分图匹配 / 最大匹配](#111-二分图匹配--最大匹配) 44 | - [11.2. 二分图最大权匹配 using KM](#112-二分图最大权匹配-using-km) 45 | - [11.3. 稳定婚姻 using 延迟认可](#113-稳定婚姻-using-延迟认可) 46 | - [11.4. 一般图最大匹配 using 带花树](#114-一般图最大匹配-using-带花树) 47 | - [11.5. 一般图最大权匹配 using 带权带花树](#115-一般图最大权匹配-using-带权带花树) 48 | - [12. 网络流](#12-网络流) 49 | - [12.1. 最大流 using Dinic](#121-最大流-using-dinic) 50 | - [12.2. 最小费用最大流 using MCMF](#122-最小费用最大流-using-mcmf) 51 | - [12.3. 上下界网络流](#123-上下界网络流) 52 | - [13. 图论杂项](#13-图论杂项) 53 | - [13.1. Kruskal 重构树](#131-kruskal-重构树) 54 | - [13.2. DSU 重构树](#132-dsu-重构树) 55 | 56 | ## 1. 图论基础 57 | 58 | ### 1.1. 邻接表 59 | 60 | - 通用化的尝试 61 | 62 | ```cpp 63 | struct edge { 64 | ll y, w; 65 | ll to() { return y; } 66 | ll dis() { return w; } 67 | }; 68 | vector a[N]; 69 | ``` 70 | 71 | ### 1.2. 前向星 72 | 73 | ```cpp 74 | struct edge { int to, w, nxt; }; // 指向,权值,下一条边 75 | vector a; 76 | int head[N]; 77 | void addedge(int x, int y, int w) { 78 | a.push_back({y, w, head[x]}); 79 | head[x] = a.size() - 1; 80 | } 81 | void init(int n) { 82 | a.clear(); 83 | fill(head, head + n, -1); 84 | } 85 | // for (int i = head[x]; i != -1; i = a[i].nxt) // 遍历 x 出发的边 86 | ``` 87 | 88 | ### 1.3. 拓扑排序 / Toposort 89 | 90 | - $O(V+E)$。 91 | 92 | ```cpp 93 | vector topo; 94 | void toposort(int n){ 95 | static int deg[N]; fill(deg,deg+n,0); 96 | static queue q; 97 | repeat(x,0,n)for(auto p:a[x])deg[p]++; 98 | repeat(i,0,n)if(deg[i]==0)q.push(i); 99 | while(!q.empty()){ 100 | int x=q.front(); q.pop(); topo.push_back(x); 101 | for(auto p:a[x])if(--deg[p]==0)q.push(p); 102 | } 103 | } 104 | ``` 105 | 106 | ## 2. 线段树优化建图 107 | 108 | - 建两棵线段树,第一棵每个结点连向其左右儿子,第二棵每个结点连向其父亲,两棵树所有叶子对应连无向边。 109 | - `add(x1, y1, x2, y2, w)` 表示 $[x_1,y_1]$ 每个结点向 $[x_2,y_2]$ 每个结点连 w 边。 110 | - `a[i+tr.n]` 表示结点 i。 111 | - 建议 10 倍内存,编号从 0 开始,$O(n\log n)$。 112 | 113 | ```cpp 114 | typedef vector node; 115 | node a[N]; int top; 116 | struct seg{ 117 | int n; 118 | void init(int inn){ 119 | for(n=1;n>=1,r>>=1){ 134 | if(~l & 1)a[(l^1)+n*2]<>=1,r>>=1){ 140 | if(~l & 1)a[x]< b.dis; 164 | } 165 | }; 166 | bool vis[N]; 167 | vector a[N]; 168 | ll dis[N]; // result 169 | void dij(int s, int n){ // s: start 170 | fill(vis, vis + n + 1, 0); 171 | fill(dis, dis + n + 1, INF); dis[s] = 0; // last[s] = -1; 172 | static priority_queue q; q.push({s, 0}); 173 | while (!q.empty()) { 174 | int x = q.top().to; q.pop(); 175 | if (vis[x]) { continue; } vis[x] = 1; 176 | for (auto i : a[x]) { 177 | int p = i.to; 178 | if (dis[p] > dis[x] + i.dis) { 179 | dis[p] = dis[x] + i.dis; 180 | q.push({p, dis[p]}); 181 | // last[p] = x; // last 可以记录最短路(倒着) 182 | } 183 | } 184 | } 185 | } 186 | ``` 187 | 188 | ### 3.2. 多源 using Floyd 189 | 190 | - $O(V^3)$ 191 | 192 | ```cpp 193 | repeat(k,0,n) 194 | repeat(i,0,n) 195 | repeat(j,0,n) 196 | f[i][j]=min(f[i][j],f[i][k]+f[k][j]); 197 | ``` 198 | 199 | - 补充:`bitset` 优化(只考虑是否可达),$O(V^3)$ 200 | 201 | ```cpp 202 | // bitset g; 203 | repeat(i,0,n) 204 | repeat(j,0,n) 205 | if(g[j][i]) 206 | g[j]|=g[i]; 207 | ``` 208 | 209 | ### 3.3. 一般单源 using SPFA 210 | 211 | - SPFA搜索中,有一个点入队 $n+1$ 次即存在负环 212 | - 编号从 0 开始,$O(VE)$ 213 | 214 | ```cpp 215 | int cnt[N]; bool vis[N]; ll h[N]; // h意思和dis差不多,但是Johnson里需要区分 216 | int n; 217 | struct node{int to; ll dis;}; 218 | vector a[N]; 219 | bool spfa(int s){ // 返回是否有负环(s为起点) 220 | repeat(i,0,n+1) 221 | cnt[i]=vis[i]=0,h[i]=inf; 222 | h[s]=0; // last[s]=-1; 223 | static deque q; q.assign(1,s); 224 | while(!q.empty()){ 225 | int x=q.front(); q.pop_front(); 226 | vis[x]=0; 227 | for(auto i:a[x]){ 228 | int p=i.to; 229 | if(h[p]>h[x]+i.dis){ 230 | h[p]=h[x]+i.dis; 231 | // last[p]=x; // last可以记录最短路(倒着) 232 | if(vis[p])continue; 233 | vis[p]=1; 234 | q.push_back(p); // 可以SLF优化 235 | if(++cnt[p]>n)return 1; 236 | } 237 | } 238 | } 239 | return 0; 240 | } 241 | bool negcycle(){ // 返回是否有负环 242 | a[n].clear(); 243 | repeat(i,0,n) 244 | a[n].push_back({i,0}); // 加超级源点 245 | return spfa(n); 246 | } 247 | ``` 248 | 249 | ### 3.4. 多源 using Johnson 250 | 251 | - SPFA + Dijkstra 实现多源最短路,编号从 0 开始,$O(VE\log E)$ 252 | 253 | ```cpp 254 | ll dis[N][N]; 255 | bool jn(){ // 返回是否成功 256 | if(negcycle())return 0; 257 | repeat(x,0,n) 258 | for(auto &i:a[x]) 259 | i.dis+=h[x]-h[i.to]; 260 | repeat(x,0,n)dij(x,dis[x]); 261 | repeat(x,0,n) 262 | repeat(p,0,n) 263 | if(dis[x][p]!=inf) 264 | dis[x][p]+=h[p]-h[x]; 265 | return 1; 266 | } 267 | ``` 268 | 269 | ### 3.5. n2 Dijkstra 270 | 271 | - $O(n^2)$,未测 272 | 273 | ```cpp 274 | bool vis[N]; int g[N][N], dis[N]; 275 | void dij(int s, int n) { 276 | fill(vis, vis + n, 0); 277 | fill(dis, dis + n + 1, inf); dis[s] = 0; 278 | repeat (i, 0, n) { 279 | int x = n; 280 | repeat (j, 0, n) if (!vis[j] && dis[j] < dis[x]) x = j; 281 | if (x == n) return; vis[x] = 1; 282 | repeat (p, 0, n) if (!vis[p]) dis[p] = min(dis[p], dis[x] + g[x][p]); 283 | } 284 | } 285 | ``` 286 | 287 | ## 4. 最小生成树 / MST 288 | 289 | ### 4.1. Kruskal 290 | 291 | - 对边长排序,然后添边,并查集判联通,$O(E\log E)$,排序是瓶颈 292 | 293 | ```cpp 294 | DSU d; 295 | struct edge { int u, v, dis; }; 296 | vector e; 297 | ll kru(int n) { 298 | ll ans = 0, cnt = 0; d.init(n); 299 | sort(e.begin(), e.end(), [](edge a, edge b) { 300 | return a.dis < b.dis; 301 | }); 302 | for (auto i : e) { 303 | int x = d[i.u], y = d[i.v]; 304 | if (x == y) continue; 305 | d[x] = d[y]; 306 | ans += i.dis; 307 | cnt++; 308 | if (cnt == n - 1) break; 309 | } 310 | if (cnt != n - 1) return -1; 311 | else return ans; 312 | } 313 | ``` 314 | 315 | ### 4.2. Boruvka 316 | 317 | - 类似Prim算法,但是可以多路增广(~~名词迷惑行为~~),$O(E\log V)$ 318 | 319 | ```cpp 320 | DSU d; 321 | struct edge{int u,v,dis;}e[200010]; 322 | ll bor(){ 323 | ll ans=0; 324 | d.init(n); 325 | e[m].dis=inf; 326 | vector b; // 记录每个联通块的增广路(名词迷惑行为) 327 | bool f=1; 328 | while(f){ 329 | b.assign(n,m); 330 | repeat(i,0,m){ 331 | int x=d[e[i].u],y=d[e[i].v]; 332 | if(x==y)continue; 333 | if(e[i].dis *e = b; 414 | int dep[N], fa[N][22]; 415 | ll sum[N][22]; 416 | void dfs(int x) { // private 417 | repeat (i, 1, 22) { 418 | fa[x][i] = fa[fa[x][i - 1]][i - 1]; 419 | sum[x][i] = U(sum[x][i - 1], sum[fa[x][i - 1]][i - 1]); 420 | } 421 | for (auto i : e[x]) { 422 | int p = i.to(); 423 | if (p == fa[x][0]) continue; 424 | fa[p][0] = x, dep[p] = dep[x] + 1; 425 | sum[p][0] = i.dis(); 426 | dfs(p); 427 | } 428 | } 429 | void init(int s) { 430 | fa[s][0] = s; 431 | dep[s] = 1; 432 | dfs(s); 433 | } 434 | int lca(int x, int y) { 435 | if (dep[x] < dep[y]) swap(x, y); 436 | while (dep[x] > dep[y]) x = fa[x][__lg(dep[x] - dep[y])]; 437 | if (x == y) return x; 438 | repeat_back (i, 0, 22) 439 | if (fa[x][i] != fa[y][i]) 440 | x = fa[x][i], y = fa[y][i]; 441 | return fa[x][0]; 442 | } 443 | int la(int x, int len) { // father ^ len (x) 444 | repeat (i, 0, 22) 445 | if (len >> i & 1) 446 | x = fa[x][i]; 447 | return x; 448 | } 449 | ll qsumla(int x, int len) { // query from x to father ^ len (x) 450 | ll ans = 0; 451 | repeat (i, 0, 22) 452 | if (len >> i & 1) { 453 | ans = U(ans, sum[x][i]); 454 | x = fa[x][i]; 455 | } 456 | return ans; 457 | } 458 | ll qsum(int x, int y) { // query from x to y 459 | int l = lca(x, y); 460 | return U(qsumla(x, dep[x] - dep[l]), qsumla(y, dep[y] - dep[l])); 461 | } 462 | } 463 | ``` 464 | 465 | #### 5.3.2. 欧拉序列建 ST 表解法 466 | 467 | - 编号从 0 开始,初始化 $O(n\log n)$,查询 $O(1)$ 468 | 469 | ```cpp 470 | int n, m; 471 | vector eu; 472 | vector e[N]; 473 | int pos[N], dep[N], len[N]; 474 | #define mininarr(a, x, y) (a[x] < a[y] ? x : y) 475 | struct RMQ { 476 | #define logN 21 477 | int f[N * 2][logN]; 478 | void build() { 479 | int n = eu.size(); 480 | repeat(i, 0, n) f[i][0] = eu[i]; 481 | repeat(k, 1, logN) 482 | repeat(i, 0, n - (1 << k) + 1) 483 | f[i][k] = mininarr(dep, f[i][k - 1], f[i + (1 << (k - 1))][k - 1]); 484 | } 485 | int query(int l, int r) { 486 | if (l > r) swap(l, r); // !! 487 | int s = __lg(r - l + 1); 488 | return mininarr(dep, f[l][s], f[r - (1 << s) + 1][s]); 489 | } 490 | } rmq; 491 | void dfs(int x, int fa = -1) { 492 | eu.push_back(x); 493 | pos[x] = eu.size() - 1; 494 | for (auto i : e[x]) { 495 | int p = i.to(); 496 | if (p == fa) continue; 497 | dep[p] = dep[x] + 1; 498 | len[p] = len[x] + i.dis(); 499 | dfs(p, x); 500 | eu.push_back(x); 501 | } 502 | } 503 | void init(int s) { 504 | eu.clear(); 505 | dep[s] = len[s] = 0; 506 | dfs(s); 507 | rmq.build(); 508 | } 509 | int lca(int x, int y) { return rmq.query(pos[x], pos[y]); } 510 | ``` 511 | 512 | #### 5.3.3. 树链剖分解法 513 | 514 | - 编号从哪开始都可以,初始化 $O(n)$,查询 $O(\log n)$ 515 | 516 | ```cpp 517 | vector a[N]; 518 | namespace lca { 519 | vector *e = a; 520 | int dep[N], dis[N], son[N], sz[N], top[N], fa[N]; // son: heaviest son, top: top of the chain 521 | void dfs1(int x) { // get (dep, sz, son, fa), private 522 | sz[x] = 1; 523 | son[x] = -1; 524 | for (auto i : e[x]) { 525 | int p = i.to(); 526 | if (p == fa[x]) continue; 527 | fa[p] = x; dep[p] = dep[x] + 1; 528 | dis[p] = dis[x] + i.dis(); 529 | dfs1(p); 530 | sz[x] += sz[p]; 531 | if (son[x] == -1 || sz[son[x]] < sz[p]) 532 | son[x] = p; 533 | } 534 | } 535 | void dfs2(int x, int tv) { // get top, private 536 | top[x] = tv; 537 | if (son[x] == -1) return; 538 | dfs2(son[x], tv); 539 | for (auto i : e[x]) { 540 | int p = i.to(); 541 | if (p == fa[x] || p == son[x]) continue; 542 | dfs2(p, p); 543 | } 544 | } 545 | void init(int s) { // s is the root 546 | fa[s] = -1; dep[s] = dis[s] = 0; 547 | dfs1(s); 548 | dfs2(s, s); 549 | } 550 | int lca(int x, int y) { 551 | while (top[x] != top[y]) 552 | if (dep[top[x]] >= dep[top[y]]) x = fa[top[x]]; 553 | else y = fa[top[y]]; 554 | return dep[x] < dep[y] ? x : y; 555 | } 556 | } 557 | ``` 558 | 559 | #### 5.3.4. Tarjan 解法 560 | 561 | - 离线算法,基于并查集 562 | - qry 和 ans 编号从 0 开始,$O(n+m)$,大常数(不看好) 563 | 564 | ```cpp 565 | vector e[N]; vector qry,q[N]; // qry输入 566 | DSU d; bool vis[N]; int ans[N]; // ans输出 567 | void dfs(int x){ 568 | vis[x]=1; 569 | for(auto i:q[x])if(vis[i.fi])ans[i.se]=d[i.fi]; 570 | for(auto p:e[x])if(!vis[p])dfs(p),d[p]=x; 571 | } 572 | void solve(int n,int s){ 573 | repeat(i,0,qry.size()){ 574 | q[qry[i].fi].push_back({qry[i].se,i}); 575 | q[qry[i].se].push_back({qry[i].fi,i}); 576 | } 577 | d.init(n); dfs(s); 578 | } 579 | ``` 580 | 581 | #### 5.3.5. 一些关于lca的问题 582 | 583 | ```cpp 584 | int length(int x,int y){ // 路径长度 585 | return dep[x]+dep[y]-2*dep[lca(x,y)]; 586 | } 587 | ``` 588 | 589 | ```cpp 590 | int intersection(int x,int y,int xx,int yy){ // 树上两条路径公共点个数 591 | int t[4]={lca(x,xx),lca(x,yy),lca(y,xx),lca(y,yy)}; 592 | sort(t,t+4,[](int x,int y){return dep[x] e[N]; 607 | int dep[N],son[N],sz[N],top[N],fa[N]; 608 | int id[N],arcid[N],idcnt; // id[x]:结点x在树剖序中的位置,arcid相反 609 | void dfs1(int x){ 610 | sz[x]=1; son[x]=-1; dep[x]=dep[fa[x]]+1; 611 | for(auto p:e[x]){ 612 | if(p==fa[x])continue; 613 | fa[p]=x; dfs1(p); 614 | sz[x]+=sz[p]; 615 | if(son[x]==-1 || sz[son[x]]update(id[top[x]],id[x],d); 639 | x=fa[top[x]]; 640 | } 641 | if(dep[x]>dep[y])swap(x,y); 642 | tr->update(id[x],id[y],d); 643 | } 644 | ll qchain(int x,int y){ 645 | ll ans=0; 646 | while(top[x]!=top[y]){ 647 | if(dep[top[x]]query(id[top[x]],id[x]); 649 | x=fa[top[x]]; 650 | } 651 | if(dep[x]>dep[y])swap(x,y); 652 | ans+=tr->query(id[x],id[y]); 653 | return ans; 654 | } 655 | void uptree(int x,int d){ 656 | tr->update(id[x],id[x]+sz[x]-1,d); 657 | } 658 | ll qtree(int x){ 659 | return tr->query(id[x],id[x]+sz[x]-1); 660 | } 661 | ``` 662 | 663 | ### 5.5. 树分治 664 | 665 | #### 5.5.1. 点分治 666 | 667 | - 每次找树的重心(最大子树最小的点),去掉它后对所有子树进行相同操作 668 | - 一般 $O(n\log n)$ 669 | - 例:luogu P3806,带边权的树,询问长度为 $q_i$ 的路径是否存在 670 | 671 | ```cpp 672 | const int debug = 1; 673 | vector a[N]; 674 | bool vis[N]; 675 | vector q; // q[i].fi: query; q[i].se: answer 676 | namespace center { 677 | vector rec; 678 | int sz[N], maxx[N]; 679 | void dfs(int x, int fa = -1) { 680 | rec << x; 681 | sz[x] = 1; 682 | maxx[x] = 0; 683 | for (auto i : a[x]) { 684 | int p = i.fi; 685 | if (p != fa && !vis[p]) { 686 | dfs(p, x); 687 | sz[x] += sz[p]; 688 | maxx[x] = max(maxx[x], sz[p]); 689 | } 690 | } 691 | } 692 | int get(int x) { // get center 693 | rec.clear(); 694 | dfs(x); 695 | int n = sz[x], ans = x; 696 | for (auto x : rec) { 697 | maxx[x] = max(maxx[x], n - sz[x]); 698 | if (maxx[x] < maxx[ans]) ans = x; 699 | } 700 | return ans; 701 | } 702 | } // namespace center 703 | vector rec; 704 | void getdist(int x, int dis, int fa = -1) { 705 | if (dis < 10000010) rec << dis; 706 | for (auto i : a[x]) { 707 | int p = i.fi; 708 | if (p != fa && !vis[p]) { 709 | getdist(p, dis + i.se, x); 710 | } 711 | } 712 | } 713 | unordered_set bkt; 714 | void dfs(int x) { 715 | x = center::get(x); 716 | bkt.clear(); 717 | bkt.insert(0); 718 | vis[x] = 1; 719 | for (auto i : a[x]) { // 这部分统计各个子树的信息并更新答案 720 | int p = i.fi; 721 | if (!vis[p]) { 722 | rec.clear(); 723 | getdist(p, i.se); 724 | for (auto i : rec) { 725 | for (auto &j : q) 726 | if (bkt.count(j.fi - i)) j.se = 1; 727 | } 728 | for (auto i : rec) bkt.insert(i); 729 | } 730 | } 731 | for (auto i : a[x]) { // 这部分进一步分治 732 | int p = i.fi; 733 | if (!vis[p]) { 734 | dfs(p); 735 | } 736 | } 737 | } 738 | ``` 739 | 740 | ### 5.6. 虚树 741 | 742 | - $O(k\log n)$ 处理出指定 k 个点及其两两 lca 构成的树,原理是单调栈 743 | - `pos[x]` 表示 DFS 序中 x 的位置,`lab[x]` 表示 x 是否为指定点 744 | - `tr` 表示虚树,`v` 是指定点序列(input) 745 | - 基于 lca,预处理为 `lca::init()` 以及 `pos[]` 746 | 747 | ```cpp 748 | vector e[N],v; // v: input 749 | int pos[N]; 750 | vector stk,rec; 751 | vector tr[N]; 752 | bool lab[N]; 753 | #define r stk.rbegin() 754 | void add(){ 755 | tr[r[1]].push_back({r[0],dep[r[0]]-dep[r[1]]}); // tr[x][i].second is the length of the edge 756 | rec.push_back(r[0]); 757 | stk.pop_back(); 758 | } 759 | void lastdfs(int x,int fa){ 760 | ; 761 | } 762 | void vtree(){ 763 | sort(v.begin(),v.end(),[](int x,int y){ 764 | return pos[x]pos[r[1]]) 772 | stk.insert(stk.end()-1,l); 773 | add(); 774 | } 775 | stk.push_back(i); 776 | } 777 | while(stk.size()>1)add(); 778 | // flag=true; 779 | lastdfs(1,-1); 780 | // if(flag)printf("%d\n",cost[1]); else puts("-1"); 781 | for(auto i:rec){ // clear 782 | tr[i].clear(); 783 | lab[i]=0; // cost[i]=0; up[i]=0; 784 | } 785 | } 786 | ``` 787 | 788 | ### 5.7. 树上启发式合并 789 | 790 | - 暴力方式处理子树问题 791 | - 编号无限制,$O(n\log n)$ 792 | 793 | ```cpp 794 | vector e[N]; int n; 795 | int sz[N],son[N],dep[N]; bool vis[N]; 796 | ll ans[N],sum[N]; int num[N],top,c[N]; // not fixed 797 | void initdfs(int x,int fa){ 798 | dep[x]=dep[fa]+1; sz[x]=1; 799 | for(auto p:e[x])if(p!=fa){ 800 | initdfs(p,x); sz[x]+=sz[p]; 801 | if(sz[p]>sz[son[x]])son[x]=p; 802 | } 803 | } 804 | void update(int x,int fa,int op){ 805 | sum[num[c[x]]]-=c[x]; num[c[x]]+=op; sum[num[c[x]]]+=c[x]; 806 | if(sum[top+1])top++; if(!sum[top])top--; 807 | for(auto p:e[x])if(p!=fa && !vis[p]) 808 | update(p,x,op); 809 | } 810 | void dfs(int x,int fa,int hs){ 811 | for(auto p:e[x])if(p!=fa && p!=son[x]) 812 | dfs(p,x,0); 813 | if(son[x])dfs(son[x],x,1),vis[son[x]]=1; 814 | update(x,fa,1); 815 | vis[son[x]]=0; ans[x]=sum[top]; 816 | if(!hs)update(x,fa,-1); 817 | } 818 | // initdfs(s,-1); dfs(s,-1,1); 819 | ``` 820 | 821 | ## 6. 联通性相关 822 | 823 | ### 6.1. 强联通分量 SCC + 缩点 824 | 825 | Tarjan 826 | 827 | - co: 染色结果(点 i 缩点后为 `co[i]`)。 828 | - w: 点权(缩点后原地更新)。 829 | - sz: 第 i 个颜色的点数。 830 | - 编号从 0 开始,$O(V+E)$。 831 | 832 | ```cpp 833 | vector a[N]; 834 | stack stk; 835 | bool vis[N], instk[N]; 836 | int dfn[N], low[N], co[N], w[N]; 837 | vector sz; 838 | int n, dcnt; 839 | void dfs(int x) { // Tarjan 840 | vis[x] = instk[x] = 1; stk.push(x); 841 | dfn[x] = low[x] = ++dcnt; 842 | for(auto p : a[x]) { 843 | if (!vis[p]) dfs(p); 844 | if (instk[p]) low[x] = min(low[x], low[p]); 845 | } 846 | if (low[x] == dfn[x]) { 847 | int t; sz.push_back(0); 848 | do { 849 | t = stk.top(); 850 | stk.pop(); 851 | instk[t] = 0; 852 | sz.back() += w[t]; 853 | co[t] = sz.size() - 1; 854 | } while (t != x); 855 | } 856 | } 857 | void getscc() { 858 | fill(vis, vis + n, 0); 859 | sz.clear(); 860 | repeat (i, 0, n) if (!vis[i]) dfs(i); 861 | } 862 | void shrink() { // result inplace 863 | static vector eset; 864 | eset.clear(); 865 | getscc(); 866 | repeat (i, 0, n) 867 | for (auto p : a[i]) 868 | if (co[i] != co[p]) 869 | eset.push_back({co[i], co[p]}); 870 | n = sz.size(); 871 | repeat (i, 0, n){ 872 | a[i].clear(); 873 | w[i] = sz[i]; 874 | } 875 | for(auto i : eset){ 876 | a[i.fi].push_back(i.se); 877 | // a[i.se].push_back(i.fi); 878 | } 879 | } 880 | ``` 881 | 882 | - 例题:给一个有向图,连最少的边使其变为scc。解:scc缩点后输出 $\max(\sum\limits_i[indeg[i]=0],\sum\limits_i[outdeg[i]=0])$,特判只有一个scc的情况。 883 | 884 | Kosaraju(缩点同上) 885 | 886 | - 编号从 1 开始,$O(V+E)$。 887 | 888 | ```cpp 889 | int co[N],sz[N]; // (output) co: vertex color, sz: number of vertices of color i 890 | bool vis[N]; vector q; // private 891 | vector a[N],b[N]; // (input) a: graph, b:invgraph 892 | int cnt; // (output) cnt: color number 893 | void dfs1(int x){ 894 | vis[x]=1; 895 | for(auto p:a[x])if(!vis[p])dfs1(p); 896 | q.push_back(x); 897 | } 898 | void dfs2(int x,int c){ 899 | vis[x]=0; co[x]=c; sz[c]++; 900 | for(auto p:b[x])if(vis[p])dfs2(p,c); 901 | } 902 | void getscc(int n){ 903 | fill(vis,vis+n+1,0); 904 | fill(sz,sz+n+1,0); 905 | cnt=0; q.clear(); 906 | repeat(i,1,n+1)if(!vis[i])dfs1(i); 907 | reverse(q.begin(),q.end()); 908 | for(auto i:q)if(vis[i])dfs2(i,++cnt); 909 | } 910 | ``` 911 | 912 | ### 6.2. 边双连通分量 using Tarjan 913 | 914 | - 编号从0开始,$O(V+E)$ 915 | 916 | ```cpp 917 | void dfs(int x,int fa){ // Tarjan求边双联通分量 918 | vis[x]=instk[x]=1; stk.push(x); 919 | dfn[x]=low[x]=++dcnt; 920 | for(auto p:a[x]) 921 | if(p!=fa){ 922 | if(!vis[p])dfs(p,x); 923 | if(instk[p])low[x]=min(low[x],low[p]); 924 | } 925 | else fa=-1; // 处理重边 926 | if(low[x]==dfn[x]){ 927 | int t; sz.push_back(0); // 记录 928 | do{ 929 | t=stk.top(); 930 | stk.pop(); 931 | instk[t]=0; 932 | sz.back()+=w[t]; // 记录 933 | co[t]=sz.size()-1; // 染色 934 | }while(t!=x); 935 | } 936 | } 937 | void getscc(){ 938 | fill(vis,vis+n,0); 939 | sz.clear(); 940 | repeat(i,0,n)if(!vis[i])dfs(i,-1); 941 | } 942 | // 全局变量,shrink()同scc 943 | ``` 944 | 945 | ### 6.3. 割点 / 割顶 946 | 947 | - Tarjan 948 | 949 | ```cpp 950 | bool vis[N],cut[N]; // cut即结果,cut[i]表示i是否为割点 951 | int dfn[N],low[N]; 952 | int dcnt; // 时间戳 953 | void dfs(int x,bool isroot=1){ 954 | if(vis[x])return; vis[x]=1; 955 | dfn[x]=low[x]=++dcnt; 956 | int ch=0; cut[x]=0; 957 | for(auto p:a[x]){ 958 | if(!vis[p]){ 959 | dfs(p,0); 960 | low[x]=min(low[x],low[p]); 961 | if(!isroot && low[p]>=dfn[x]) 962 | cut[x]=1; 963 | ch++; 964 | } 965 | low[x]=min(low[x],dfn[p]); 966 | } 967 | if(isroot && ch>=2) // 根结点判断方法 968 | cut[x]=1; 969 | } 970 | ``` 971 | 972 | ## 7. 2-Sat 问题 973 | 974 | - 有 $2n$ 个顶点,其中顶点 $2i$ 和顶点 $2i+1$ 中能且仅能选一个,边 (u, v) 表示选了 u 就必须选 v,求一个可行解 975 | - 暴力版,可以跑出字典序最小的解,编号从 0 开始,$O(VE)$,(~~但是难以跑到上界~~) 976 | 977 | ```cpp 978 | struct twosat{ // 暴力版 979 | int n; 980 | vector g[N*2]; 981 | bool mark[N*2]; // mark即结果,表示是否选择了这个点 982 | int s[N],c; 983 | bool dfs(int x){ // private 984 | if(mark[x^1])return 0; 985 | if(mark[x])return 1; 986 | mark[s[c++]=x]=1; 987 | for(auto p:g[x]) 988 | if(!dfs(p)) 989 | return 0; 990 | return 1; 991 | } 992 | void init(int _n){ 993 | n=_n; 994 | for(int i=0;i0)mark[s[--c]]=0; 1009 | if(!dfs(i^1))return 0; 1010 | } 1011 | } 1012 | return 1; 1013 | } 1014 | }ts; 1015 | ``` 1016 | 1017 | - SCC 操作,编号从 1 开始,$O(V+E)$,注意 N 需要手动两倍 1018 | 1019 | ```cpp 1020 | bool ans[N]; // shows one solution if possible 1021 | bool twosat(int n){ // return whether possible 1022 | getscc(n*2); 1023 | repeat(i,1,n+1) 1024 | if(co[i]==co[i+n])return 0; 1025 | repeat(i,1,n+1) 1026 | ans[i]=(co[i] a[N],b[N],tr[N]; // tr: result 1040 | int fa[N],dfn[N],dcnt,arcdfn[N]; 1041 | int c[N],best[N],sm[N],im[N]; // im: result 1042 | void init(int n){ 1043 | dcnt=0; 1044 | iota(c,c+n+1,0); 1045 | repeat(i,1,n+1){ 1046 | tr[i].clear(); 1047 | a[i].clear(); 1048 | b[i].clear(); 1049 | } 1050 | repeat(i,1,n+1)sm[i]=best[i]=i; 1051 | fill(dfn,dfn+n+1,0); 1052 | } 1053 | void dfs(int u){ 1054 | dfn[u]=++dcnt; arcdfn[dcnt]=u; 1055 | for(auto v:a[u])if(!dfn[v]){fa[v]=u; dfs(v);} 1056 | } 1057 | int find(int x){ 1058 | if(c[x]==x)return x; 1059 | int &f=c[x],rt=find(f); 1060 | if(dfn[sm[best[x]]]>dfn[sm[best[f]]]) 1061 | best[x]=best[f]; 1062 | return f=rt; 1063 | } 1064 | void solve(int s){ 1065 | dfs(s); 1066 | repeat_back(i,2,dcnt+1){ 1067 | int x=arcdfn[i],mn=dcnt+1; 1068 | for(auto u:b[x]){ 1069 | if(!dfn[u])continue; 1070 | find(u); mn=min(mn,dfn[sm[best[u]]]); 1071 | } 1072 | c[x]=fa[x]; 1073 | tr[sm[x]=arcdfn[mn]].push_back(x); 1074 | x=arcdfn[i-1]; 1075 | for(auto u:tr[x]){ 1076 | find(u); 1077 | if(sm[best[u]]!=x)im[u]=best[u]; 1078 | else im[u]=x; 1079 | } 1080 | tr[x].clear(); 1081 | } 1082 | repeat(i,2,dcnt+1){ 1083 | int u=arcdfn[i]; 1084 | if(im[u]!=sm[u])im[u]=im[im[u]]; 1085 | tr[im[u]].push_back(u); 1086 | } 1087 | } 1088 | ``` 1089 | 1090 | ## 9. 图上的NP问题 1091 | 1092 | ### 9.1. 最大团 and 极大团计数 1093 | 1094 | - 求最大团顶点数(和最大团),`g[][]` 编号从 0 开始,$O(\exp)$ 1095 | 1096 | ```cpp 1097 | int g[N][N],f[N][N],v[N],Max[N],n,ans; // g[][]是邻接矩阵,n是顶点数 1098 | // vector rec,maxrec; // maxrec是最大团 1099 | bool dfs(int x,int cur){ 1100 | if(cur==0) 1101 | return x>ans; 1102 | repeat(i,0,cur){ 1103 | int u=f[x][i],k=0; 1104 | if(Max[u]+x<=ans)return 0; 1105 | repeat(j,i+1,cur) 1106 | if(g[u][f[x][j]]) 1107 | f[x+1][k++]=f[x][j]; 1108 | // rec.push_back(u); 1109 | if(dfs(x+1,k))return 1; 1110 | // rec.pop_back(); 1111 | } 1112 | return 0; 1113 | } 1114 | void solve(){ 1115 | ans=0; // maxrec.clear(); 1116 | repeat_back(i,0,n){ 1117 | int k=0; 1118 | repeat(j,i+1,n) 1119 | if(g[i][j]) 1120 | f[1][k++]=j; 1121 | // rec.clear(); rec.push_back(i); 1122 | if(dfs(1,k)){ 1123 | ans++; 1124 | // maxrec=rec; 1125 | } 1126 | Max[i]=ans; 1127 | } 1128 | } 1129 | ``` 1130 | 1131 | - 求极大团个数(和所有极大团),`g[][]` 的编号从 1 开始!$O(\exp)$ 1132 | 1133 | ```cpp 1134 | int g[N][N],n; 1135 | // vector rec; // 存当前极大团 1136 | int ans,some[N][N],none[N][N]; // some是未搜索的点,none是废除的点 1137 | void dfs(int d,int sn,int nn){ 1138 | if(sn==0 && nn==0) 1139 | ans++; // 此时rec是其中一个极大图 1140 | // if(ans>1000)return; // 题目要求_(:зゝ∠)_ 1141 | int u=some[d][0]; 1142 | for(int i=0;i a; // 存独立集 1177 | #define np (1<(); q.push(0); 1181 | while(!q.empty()){ 1182 | int x=q.front(); q.pop(); 1183 | for(auto i:a){ 1184 | int p=x|i; 1185 | if(p==np-1)return dis[x]+1; 1186 | if(dis[p]>dis[x]+1){ 1187 | dis[p]=dis[x]+1; 1188 | q.push(p); 1189 | } 1190 | } 1191 | } 1192 | return 0; 1193 | } 1194 | int solve(){ // 返回最小染色数 1195 | mst(g,0); 1196 | for(auto i:eset){ 1197 | int x=i.fi,y=i.se; 1198 | g[x]|=1< a[N], b[N]; // a: input, b: the block forest 1233 | int n, bn; // n: input, bn = |B| 1234 | namespace cactus { 1235 | bool vis[N]; 1236 | ll fa[N], lab[N], dep[N], dis[N]; 1237 | bool iscactus; 1238 | ll cyclen[N]; 1239 | void dfs(int x) { // private 1240 | vis[x] = 1; 1241 | for (auto i : a[x]) { 1242 | int p = i.to(); 1243 | if (p == fa[x]) continue; 1244 | if (!vis[p]) { 1245 | fa[p] = x; dep[p] = dep[x] + 1; 1246 | dis[p] = dis[x] + i.dis(); 1247 | dfs(p); 1248 | lab[x] += lab[p]; 1249 | if (lab[p] == 0) b[x].push_back({p, i.dis()}); 1250 | } 1251 | else if (dep[p] < dep[x]) { // find a cycle 1252 | lab[x]++; lab[p]--; 1253 | // cycle size: dep[x] - dep[p] + 1 1254 | // cycle length: dis[x] - dis[p] + i.second 1255 | int u = bn++; 1256 | cyclen[u] = dis[x] - dis[p] + i.dis(); 1257 | for (int k = x; k != p; k = fa[k]) { 1258 | ll d = dis[k] - dis[p]; 1259 | b[u].push_back({k, min(d, cyclen[u] - d)}); 1260 | } 1261 | b[p].push_back({u, 0}); 1262 | } 1263 | } 1264 | if (lab[x] >= 2) iscactus = 0; 1265 | } 1266 | void init(int s, int n) { 1267 | bn = n; 1268 | repeat (i, 0, n) vis[i] = false, lab[i] = dep[i] = dis[i] = 0; 1269 | iscactus = 1; fa[s] = -1; 1270 | dfs(s); 1271 | lca::init(s); 1272 | } 1273 | ll length(int x, int y) { 1274 | int l = lca::lca(x, y); 1275 | ll ans = 0; 1276 | if (l >= n) { 1277 | ans += lca::qsumla(x, lca::dep[x] - lca::dep[l] - 1); 1278 | x = lca::la(x, lca::dep[x] - lca::dep[l] - 1); 1279 | ans += lca::qsumla(y, lca::dep[y] - lca::dep[l] - 1); 1280 | y = lca::la(y, lca::dep[y] - lca::dep[l] - 1); 1281 | ll t = abs(dis[x] - dis[y]); 1282 | ans += min(t, cyclen[l] - t); 1283 | } else { 1284 | ans = lca::qsum(x, y); 1285 | } 1286 | return ans; 1287 | } 1288 | } 1289 | ``` 1290 | 1291 | ## 11. 二分图 1292 | 1293 | ### 11.1. 二分图匹配 / 最大匹配 1294 | 1295 | - 匈牙利 / hungarian,左右顶点编号从 0 开始,$O(VE)$ 1296 | 1297 | ```cpp 1298 | vector a[N]; // a: input, the left vertex x is connected to the right vertex a[x][i] 1299 | int dcnt,mch[N],dfn[N]; // mch: output, the right vertex p is connected to the left vertex mch[p] 1300 | bool dfs(int x){ 1301 | for(auto p:a[x]){ 1302 | if(dfn[p]!=dcnt){ 1303 | dfn[p]=dcnt; 1304 | if(mch[p]==-1 || dfs(mch[p])){ 1305 | mch[p]=x; 1306 | return 1; 1307 | } 1308 | } 1309 | } 1310 | return 0; 1311 | } 1312 | int hun(int n,int m){ // n,m: the number of the left/right vertexes. return max matching 1313 | int ans=0; 1314 | repeat(i,0,m)mch[i]=-1; 1315 | repeat(i,0,n){ 1316 | dcnt++; 1317 | if(dfs(i))ans++; 1318 | } 1319 | return ans; 1320 | } 1321 | ``` 1322 | 1323 | - HK 算法 / Hopcroft-karp,左顶点编号从 0 开始,右顶点编号从 n 开始,$O(E\sqrt V)$ 1324 | 1325 | ```cpp 1326 | vector a[N]; // a: input, the left vertex x is connected to the right vertex a[x][i] 1327 | int mch[N*2],dep[N*2]; // mch: output, the vertex p is connected to the vertex mch[p] (p could be either left or right vertex) 1328 | bool bfs(int n,int m){ 1329 | static queue q; 1330 | fill(dep,dep+n+m,0); 1331 | bool flag=0; 1332 | repeat(i,0,n)if(mch[i]==-1)q.push(i); 1333 | while(!q.empty()){ 1334 | int x=q.front(); q.pop(); 1335 | for(auto p:a[x]){ 1336 | if(!dep[p]){ 1337 | dep[p]=dep[x]+1; 1338 | if(mch[p]==-1)flag=1; 1339 | else dep[mch[p]]=dep[p]+1,q.push(mch[p]); 1340 | } 1341 | } 1342 | } 1343 | return flag; 1344 | } 1345 | bool dfs(int x){ 1346 | for(auto p:a[x]){ 1347 | if(dep[p]!=dep[x]+1) continue; 1348 | dep[p]=0; 1349 | if(mch[p]==-1 || dfs(mch[p])) { 1350 | mch[x]=p; mch[p]=x; 1351 | return 1; 1352 | } 1353 | } 1354 | return 0; 1355 | } 1356 | int solve(int n,int m){ // n,m: the number of the left/right vertexes. return max matching 1357 | int ans=0; 1358 | fill(mch,mch+n+m,-1); 1359 | while(bfs(n,m)){ 1360 | repeat(i,0,n) 1361 | if(mch[i]==-1 && dfs(i)) 1362 | ans++; 1363 | } 1364 | return ans; 1365 | } 1366 | ``` 1367 | 1368 | ### 11.2. 二分图最大权匹配 using KM 1369 | 1370 | - 求满二分图的最大权匹配,如果没有边就建零边。 1371 | - 输入 n, g($n\times n$ 邻接矩阵)。 1372 | - 编号从 0 开始,$O(n^3)$。 1373 | 1374 | ```cpp 1375 | int n; 1376 | ll g[N][N]; 1377 | namespace km { 1378 | int mx[N], my[N], pre[N]; 1379 | bool visx[N], visy[N]; 1380 | ll lx[N], ly[N], slack[N]; 1381 | queue q; 1382 | bool check(int v) { 1383 | visy[v] = true; 1384 | if (my[v] != -1) { 1385 | q.push(my[v]); 1386 | visx[my[v]] = true; 1387 | return false; 1388 | } 1389 | while (v != -1) { 1390 | my[v] = pre[v]; 1391 | swap(v, mx[pre[v]]); 1392 | } 1393 | return true; 1394 | } 1395 | void bfs(int i) { 1396 | while (!q.empty()) q.pop(); 1397 | q.push(i); 1398 | visx[i] = true; 1399 | while (1) { 1400 | while (!q.empty()) { 1401 | int x = q.front(); q.pop(); 1402 | repeat (y, 0, n) if (!visy[y]) { 1403 | ll delta = lx[x] + ly[y] - g[x][y]; 1404 | if (slack[y] >= delta) { 1405 | pre[y] = x; 1406 | if (delta) { 1407 | slack[y] = delta; 1408 | } else if (check(y)) { 1409 | return; 1410 | } 1411 | } 1412 | } 1413 | } 1414 | ll a = INF; 1415 | repeat (j, 0, n) if (!visy[j]) 1416 | a = min(a, slack[j]); 1417 | repeat (j, 0, n) { 1418 | if (visx[j]) lx[j] -= a; 1419 | if (visy[j]) { 1420 | ly[j] += a; 1421 | } else { 1422 | slack[j] -= a; 1423 | } 1424 | } 1425 | repeat (j, 0, n) 1426 | if (!visy[j] && slack[j] == 0 && check(j)) 1427 | return; 1428 | } 1429 | } 1430 | ll solve() { 1431 | ll res = 0; 1432 | repeat (i, 0, n) { 1433 | mx[i] = my[i] = -1; 1434 | pre[i] = 0; 1435 | lx[i] = -INF; ly[i] = 0; 1436 | repeat (k, 0, n) 1437 | lx[i] = max(lx[i], g[i][k]); 1438 | } 1439 | for (int i = 0; i < n; i++) { 1440 | repeat (k, 0, n) { 1441 | slack[k] = INF; 1442 | visx[k] = visy[k] = 0; 1443 | } 1444 | bfs(i); 1445 | } 1446 | for (int i = 0; i < n; i++) { 1447 | res += g[i][mx[i]]; 1448 | } 1449 | return res; 1450 | } 1451 | }; 1452 | ``` 1453 | 1454 | ### 11.3. 稳定婚姻 using 延迟认可 1455 | 1456 | - 稳定意味着不存在一对不是情侣的男女,都认为当前伴侣不如对方 1457 | - 编号从 0 开始,$O(n^2)$ 1458 | 1459 | ```cpp 1460 | struct node{ 1461 | int s[N]; // s的值给定 1462 | // 对男生来说是女生编号排序 1463 | // 对女生来说是男生的分数 1464 | int now; // 选择的伴侣编号 1465 | }a[N],b[N]; // 男生,女生 1466 | int tr[N]; // 男生尝试表白了几次 1467 | queue q; // 单身狗(男)排队 1468 | bool match(int x,int y){ // 配对,返回是否成功 1469 | int x0=b[y].now; 1470 | if(x0!=-1){ 1471 | if(b[y].s[x](); 1481 | repeat(i,0,n){ 1482 | b[i].now=-1; 1483 | q.push(i); 1484 | tr[i]=0; 1485 | } 1486 | while(!q.empty()){ 1487 | int x=q.front(); q.pop(); 1488 | int y=a[x].s[tr[x]++]; // 下一个最中意女生 1489 | if(!match(x,y)) 1490 | q.push(x); // 下次努力 1491 | } 1492 | } 1493 | ``` 1494 | 1495 | ### 11.4. 一般图最大匹配 using 带花树 1496 | 1497 | - 对于一个无向图,找最多的边使得这些边两两无公共端点 1498 | - 编号从 1 开始,$O(n^3)$ 1499 | 1500 | ```cpp 1501 | int n; DSU d; 1502 | deque q; vector e[N]; 1503 | int mch[N],vis[N],dfn[N],fa[N],dcnt=0; 1504 | int lca(int x,int y){ 1505 | dcnt++; 1506 | while(1){ 1507 | if(x==0)swap(x,y); x=d[x]; 1508 | if(dfn[x]==dcnt)return x; 1509 | else dfn[x]=dcnt,x=fa[mch[x]]; 1510 | } 1511 | } 1512 | void shrink(int x,int y,int p){ 1513 | while(d[x]!=p){ 1514 | fa[x]=y; y=mch[x]; 1515 | if(vis[y]==2)vis[y]=1,q.push_back(y); 1516 | if(d[x]==x)d[x]=p; 1517 | if(d[y]==y)d[y]=p; 1518 | x=fa[y]; 1519 | } 1520 | } 1521 | bool match(int s){ 1522 | d.init(n); fill(fa,fa+n+1,0); 1523 | fill(vis,vis+n+1,0); vis[s]=1; 1524 | q.assign(1,s); 1525 | while(!q.empty()){ 1526 | int x=q.front(); q.pop_front(); 1527 | for(auto p:e[x]){ 1528 | if(d[x]==d[p] || vis[p]==2)continue; 1529 | if(!vis[p]){ 1530 | vis[p]=2; fa[p]=x; 1531 | if(!mch[p]){ 1532 | for(int now=p,last,tmp;now;now=last){ 1533 | last=mch[tmp=fa[now]]; 1534 | mch[now]=tmp,mch[tmp]=now; 1535 | } 1536 | return 1; 1537 | } 1538 | vis[mch[p]]=1; q.push_back(mch[p]); 1539 | } 1540 | else if(vis[p]==1){ 1541 | int l=lca(x,p); 1542 | shrink(x,p,l); 1543 | shrink(p,x,l); 1544 | } 1545 | } 1546 | } 1547 | return 0; 1548 | } 1549 | int solve(){ // 返回匹配数,mch[] 是匹配结果(即匹配 x 和 mch[x]),==0 表示不匹配 1550 | int ans=0; fill(mch,mch+n+1,0); 1551 | repeat(i,1,n+1)ans+=(!mch[i] && match(i)); 1552 | return ans; 1553 | } 1554 | ``` 1555 | 1556 | ### 11.5. 一般图最大权匹配 using 带权带花树 1557 | 1558 | - ~~它带什么花跟我有什么关系。~~ 1559 | - 编号从 1 开始,$O(n^3)$。 1560 | 1561 | ```cpp 1562 | struct edge{int u,v,w;}; 1563 | int n,n_x; 1564 | edge g[N*2][N*2]; 1565 | int lab[N*2]; 1566 | int match[N*2],slack[N*2],st[N*2],pa[N*2]; 1567 | int flower_from[N*2][N],S[N*2],vis[N*2]; 1568 | vector flower[N*2]; 1569 | queue q; 1570 | int e_delta(const edge &e){ 1571 | return lab[e.u]+lab[e.v]-g[e.u][e.v].w*2; 1572 | } 1573 | void update_slack(int u,int x){ 1574 | if(!slack[x] || e_delta(g[u][x])0 && st[u]!=x && S[st[u]]==0)update_slack(u,x); 1580 | } 1581 | void q_push(int x){ 1582 | if(x<=n)q.push(x); 1583 | else repeat(i,0,flower[x].size())q_push(flower[x][i]); 1584 | } 1585 | void set_st(int x,int b){ 1586 | st[x]=b; 1587 | if(x>n)repeat(i,0,flower[x].size()) 1588 | set_st(flower[x][i],b); 1589 | } 1590 | int get_pr(int b,int xr){ 1591 | int pr=find(flower[b].begin(),flower[b].end(),xr)-flower[b].begin(); 1592 | if(pr%2==1){ 1593 | reverse(flower[b].begin()+1,flower[b].end()); 1594 | return (int)flower[b].size()-pr; 1595 | }else return pr; 1596 | } 1597 | void set_match(int u,int v){ 1598 | match[u]=g[u][v].v; 1599 | if(u>n){ 1600 | edge e=g[u][v]; 1601 | int xr=flower_from[u][e.u],pr=get_pr(u,xr); 1602 | for(int i=0;in_x)++n_x; 1631 | lab[b]=0,S[b]=0; 1632 | match[b]=match[lca]; 1633 | flower[b].clear(); 1634 | flower[b].push_back(lca); 1635 | for(int x=u,y;x!=lca;x=st[pa[y]]) 1636 | flower[b].push_back(x),flower[b].push_back(y=st[match[x]]),q_push(y); 1637 | reverse(flower[b].begin()+1,flower[b].end()); 1638 | for(int x=v,y;x!=lca;x=st[pa[y]]) 1639 | flower[b].push_back(x),flower[b].push_back(y=st[match[x]]),q_push(y); 1640 | set_st(b,b); 1641 | repeat(x,1,n_x+1)g[b][x].w=g[x][b].w=0; 1642 | repeat(x,1,n+1)flower_from[b][x]=0; 1643 | repeat(i,0,flower[b].size()){ 1644 | int xs=flower[b][i]; 1645 | for(int x=1;x<=n_x;++x) 1646 | if(g[b][x].w==0 || e_delta(g[xs][x])(); 1689 | for(int x=1;x<=n_x;++x) 1690 | if(st[x]==x && !match[x])pa[x]=0,S[x]=0,q_push(x); 1691 | if(q.empty())return false; 1692 | while(1){ 1693 | while(q.size()){ 1694 | int u=q.front(); q.pop(); 1695 | if(S[st[u]]==1)continue; 1696 | repeat(v,1,n+1) 1697 | if(g[u][v].w>0 && st[u]!=st[v]){ 1698 | if(e_delta(g[u][v])==0){ 1699 | if(on_found_edge(g[u][v]))return true; 1700 | }else update_slack(u,st[v]); 1701 | } 1702 | } 1703 | int d=inf; 1704 | for(int b=n+1;b<=n_x;++b) 1705 | if(st[b]==b && S[b]==1)d=min(d,lab[b]/2); 1706 | repeat(x,1,n_x+1) 1707 | if(st[x]==x && slack[x]){ 1708 | if(S[x]==-1)d=min(d,e_delta(g[slack[x]][x])); 1709 | else if(S[x]==0)d=min(d,e_delta(g[slack[x]][x])/2); 1710 | } 1711 | repeat(u,1,n+1){ 1712 | if(S[st[u]]==0){ 1713 | if(lab[u]<=d)return 0; 1714 | lab[u]-=d; 1715 | }else if(S[st[u]]==1)lab[u]+=d; 1716 | } 1717 | for(int b=n+1;b<=n_x;++b) 1718 | if(st[b]==b){ 1719 | if(S[st[b]]==0)lab[b]+=d*2; 1720 | else if(S[st[b]]==1)lab[b]-=d*2; 1721 | } 1722 | q=queue(); 1723 | repeat(x,1,n_x+1) 1724 | if(st[x]==x && slack[x] && st[slack[x]]!=x && e_delta(g[slack[x]][x])==0) 1725 | if(on_found_edge(g[slack[x]][x]))return true; 1726 | for(int b=n+1;b<=n_x;++b) 1727 | if(st[b]==b && S[b]==1 && lab[b]==0)expand_blossom(b); 1728 | } 1729 | return false; 1730 | } 1731 | pair weight_blossom(){ 1732 | memset(match+1,0,sizeof(int)*n); 1733 | n_x=n; 1734 | int n_matches=0; 1735 | ll tot_weight=0; 1736 | for(int u=0;u<=n;++u)st[u]=u,flower[u].clear(); 1737 | int w_max=0; 1738 | repeat(u,1,n+1) 1739 | repeat(v,1,n+1){ 1740 | flower_from[u][v]=(u==v?u:0); 1741 | w_max=max(w_max,g[u][v].w); 1742 | } 1743 | repeat(u,1,n+1)lab[u]=w_max; 1744 | while(matching())++n_matches; 1745 | repeat(u,1,n+1) 1746 | if(match[u] && match[u]().max() >> 1; 1778 | struct edge { int to, w, nxt; }; 1779 | vector a; int head[N], cur[N]; 1780 | int n, s, t, dep[N]; 1781 | queue q; bool inque[N]; 1782 | void ae(int x, int y, int w) { // add edge, private 1783 | a.push_back({y, w, head[x]}); 1784 | head[x] = a.size() - 1; 1785 | } 1786 | bool bfs() { // private 1787 | fill(dep, dep + n, inf); dep[s] = 0; 1788 | copy(head, head + n, cur); 1789 | q = queue(); q.push(s); 1790 | while (!q.empty()) { 1791 | int x = q.front(); q.pop(); inque[x] = 0; 1792 | for (int i = head[x]; i != -1; i = a[i].nxt) { 1793 | int p = a[i].to; 1794 | if (dep[p] > dep[x] + 1 && a[i].w) { 1795 | dep[p] = dep[x] + 1; 1796 | if (inque[p] == 0) { 1797 | inque[p] = 1; 1798 | q.push(p); 1799 | } 1800 | } 1801 | } 1802 | } 1803 | return dep[t] != inf; 1804 | } 1805 | int dfs(int x, int flow) { // private 1806 | int now, ans = 0; 1807 | if (x == t) return flow; 1808 | for (int &i = cur[x]; i != -1; i = a[i].nxt) { 1809 | int p = a[i].to; 1810 | if (a[i].w && dep[p] == dep[x] + 1) 1811 | if ((now = dfs(p, min(flow, a[i].w)))) { 1812 | a[i].w -= now; 1813 | a[i ^ 1].w += now; 1814 | ans += now, flow -= now; 1815 | if (flow == 0) break; 1816 | } 1817 | } 1818 | return ans; 1819 | } 1820 | void init(int _n) { // init with n nodes 1821 | n = _n + 1; a.clear(); 1822 | fill(head, head + n, -1); 1823 | fill(inque, inque + n, 0); 1824 | } 1825 | void add_dir(int x, int y, int w) { ae(x, y, w), ae(y, x, 0); } // add directed edge 1826 | void add_undir(int x, int y, int w) { ae(x, y, w), ae(y, x, w); } // add undirected edge 1827 | int solve(int _s, int _t) { // get max flow from s to t 1828 | s = _s, t = _t; 1829 | int ans = 0; 1830 | while (bfs()) ans += dfs(s, inf); 1831 | return ans; 1832 | } 1833 | void print() { 1834 | repeat (x, 0, n) { 1835 | cout << x << ":"; 1836 | for (int i = head[x]; i != -1; i = a[i].nxt) if (i % 2 == 0) 1837 | cout << " (" << a[i].to << ", " << a[i ^ 1].w << " / " << a[i].w + a[i ^ 1].w << ")"; 1838 | cout << endl; 1839 | } 1840 | } 1841 | } flow; 1842 | // 先 init(n),再 add_dir / add_undir 添边,最后 solve(s, t) 1843 | ``` 1844 | 1845 | 最小割的边集 1846 | 1847 | ```cpp 1848 | struct MinCut : Flow { 1849 | bool vis[N]; 1850 | void dfs(int x) { 1851 | vis[x] = 1; 1852 | for (int i = head[x]; i != -1; i = a[i].nxt) 1853 | if (a[i].w && a[i ^ 1].w && !vis[a[i].to]) 1854 | dfs(a[i].to); 1855 | } 1856 | vector get_cut() { // after solve(s, t) 1857 | fill(vis, vis + n, 0); 1858 | dfs(s); 1859 | // vis[i] = true 表示 S 集合,否则 T 集合 1860 | // 下面的代码不太清楚对不对 1861 | vector ans; 1862 | repeat (x, 0, n) if (vis[x]) { 1863 | for (int i = head[x]; i != -1; i = a[i].nxt) 1864 | if (i % 2 == 0 && a[i].w == 0 && !vis[a[i].to]) 1865 | ans.push_back({x, a[i].to}); 1866 | } 1867 | return ans; 1868 | } 1869 | } flow; 1870 | ``` 1871 | 1872 | ### 12.2. 最小费用最大流 using MCMF 1873 | 1874 | - 费用流一般指最小费用最大流(最大费用最大流把费用取反即可) 1875 | - 编号从 0 开始,$O(VE^2)$ 1876 | 1877 | ```cpp 1878 | struct Flow { 1879 | const int inf = INT_MAX >> 1; 1880 | struct edge { int to, w, cost, nxt; }; 1881 | vector a; int head[N]; 1882 | int n, s, t, totcost; 1883 | deque q; 1884 | bool inque[N]; 1885 | int dis[N]; 1886 | struct { int to, e; } pre[N]; 1887 | void ae(int x, int y, int w, int cost) { 1888 | a.push_back({y, w, cost, head[x]}); 1889 | head[x] = a.size() - 1; 1890 | } 1891 | bool spfa() { 1892 | fill(dis, dis + n, inf); dis[s] = 0; 1893 | q.assign(1, s); 1894 | while (!q.empty()) { 1895 | int x = q.front(); q.pop_front(); 1896 | inque[x] = 0; 1897 | for (int i = head[x]; i != -1; i = a[i].nxt) { 1898 | int p = a[i].to; 1899 | if (dis[p] > dis[x] + a[i].cost && a[i].w) { 1900 | dis[p] = dis[x] + a[i].cost; 1901 | pre[p] = {x, i}; 1902 | if (inque[p] == 0) { 1903 | inque[p] = 1; 1904 | if (!q.empty() 1905 | && dis[q.front()] <= dis[p]) 1906 | q.push_back(p); 1907 | else q.push_front(p); 1908 | } 1909 | } 1910 | } 1911 | } 1912 | return dis[t] != inf; 1913 | } 1914 | void init(int _n) { 1915 | n = _n + 1; a.clear(); 1916 | fill(head, head + n, -1); 1917 | fill(inque, inque + n, 0); 1918 | } 1919 | int solve(int _s, int _t) { // 返回最大流,费用存 totcost 里 1920 | s = _s, t = _t; 1921 | int ans = 0; 1922 | totcost = 0; 1923 | while (spfa()) { 1924 | int fl = inf; 1925 | for (int i = t; i != s; i = pre[i].to) 1926 | fl = min(fl, a[pre[i].e].w); 1927 | for (int i = t; i != s; i = pre[i].to){ 1928 | a[pre[i].e].w -= fl; 1929 | a[pre[i].e ^ 1].w += fl; 1930 | } 1931 | totcost += dis[t] * fl; 1932 | ans += fl; 1933 | } 1934 | return ans; 1935 | } 1936 | } flow; 1937 | void add(int x, int y, int w, int cost) { 1938 | flow.ae(x, y, w, cost), flow.ae(y, x, 0, -cost); 1939 | } 1940 | // 先 flow.init(n),再 add 添边,最后 flow.solve(s,t) 1941 | ``` 1942 | 1943 | ### 12.3. 上下界网络流 1944 | 1945 | 上下界最大 / 小流:两遍普通最大流。 1946 | 1947 | ```cpp 1948 | struct BoundFlow: public Flow { // 从 Dinic 板子里继承 1949 | int degree[N]; 1950 | void add(int x, int y, int l, int r) { // x 到 y 的有向边,下界 l,上界 r 1951 | degree[y] += l; degree[x] -= l; 1952 | add_dir(x, y, r - l); 1953 | } 1954 | void init(int _n) { // 初始化 1955 | Flow::init(_n + 2); // 新增两点:n - 1 附加源点,n - 2 附加汇点 1956 | fill(degree, degree + n, 0); 1957 | } 1958 | bool ok() { // 无源汇可行流 1959 | int s = n - 2, t = n - 1, sum = 0; 1960 | repeat (i, 0, s) { 1961 | if (degree[i] > 0) 1962 | add_dir(s, i, degree[i]), sum += degree[i]; 1963 | else if (degree[i] < 0) 1964 | add_dir(i, t, -degree[i]); 1965 | } 1966 | return sum == solve(s, t); 1967 | } 1968 | bool ok(int s, int t) { // 有源汇可行流 1969 | add(t, s, 0, inf); 1970 | return ok(); 1971 | } 1972 | int max_flow(int s, int t) { // 有源汇最大流 1973 | if (ok(s, t) == false) return -1; 1974 | for (int i = head[n - 2]; i != -1; i = a[i].nxt) 1975 | a[i].w = a[i ^ 1].w = 0; 1976 | for (int i = head[n - 1]; i != -1; i = a[i].nxt) 1977 | a[i].w = a[i ^ 1].w = 0; 1978 | return solve(s, t); // 最小流这行改为 inf - solve(t, s) 1979 | } 1980 | } flow; 1981 | ``` 1982 | 1983 | ## 13. 图论杂项 1984 | 1985 | ### 13.1. Kruskal 重构树 1986 | 1987 | - 基于 Kruskal 算法构建的有根树。 1988 | - 在原图中从某一点出发,只走边权不超过 w 的边,可达的点集在重构树中是一棵子树,对应 DFS 序的一个区间。 1989 | - 构建过程:边权从小到大访问边 (x, y)。如果 x, y 不连通,新建点 s 连接 x, y 所在树的根,且 s 为新树的根,s 的点权为边 (x, y) 的边权。 1990 | - 查找 x 能访问的点时,树上倍增找到最远的点权不大于 w 的祖先。 1991 | - 性质:二叉树,大根堆。 1992 | - luogu P4197,编号从 1 开始,$O(E\log E)$。 1993 | 1994 | ```cpp 1995 | DSU d; 1996 | vector a[N]; 1997 | int h[N],w[N]; 1998 | vector> eset; 1999 | vector rec; int l[N],r[N],fa[N][logN]; 2000 | void dfs(int x){ 2001 | l[x]=rec.size(); rec.push_back(x); 2002 | for(auto p:a[x])fa[p][0]=x,dfs(p); 2003 | r[x]=rec.size()-1; 2004 | } 2005 | divtree tr; // 其中一行改为 repeat(i,1,n+1)tr[0][i]=a[i]=h[rec[i]]; 2006 | void kru(){ 2007 | sort(eset.begin(),eset.end(), 2008 | [](array &a,array &b){ 2009 | return a[2] a[N]; 2057 | vector ops; // input 2058 | void dfs(int x){ // private 2059 | l[x]=r[x]=cnt++; 2060 | for(auto p:a[x])dfs(p); 2061 | } 2062 | void build(int n){ 2063 | cnt=0; d.init(n); 2064 | repeat(i,0,n+1)a[i].clear(); 2065 | for(auto i:ops){ 2066 | int x=i.first,y=i.second; 2067 | x=d[x]; y=d[y]; 2068 | if(x!=y){ 2069 | a[x].push_back(y); 2070 | d[y]=d[x]; 2071 | } 2072 | } 2073 | repeat(i,1,n+1)if(d[i]==i)a[0].push_back(i); 2074 | dfs(0); d.init(n); ops.clear(); 2075 | } 2076 | void merge(int x,int y){ 2077 | x=d[x]; y=d[y]; 2078 | if(x!=y)d[y]=d[x],r[x]=r[y]; 2079 | } 2080 | void query(int x,int &L,int &R){ 2081 | x=d[x]; L=l[x]; R=r[x]; 2082 | } 2083 | }red; 2084 | ``` 2085 | --------------------------------------------------------------------------------