ACM notes
语言基础
输入输出
getline()输入
cin 和 getline()同时用时需要ignore
string s1,s2;
cin >> s1;
cin.ignore();
getline(cin, s2);
cout << s1 << " " << s2 << "\n";
格式输出
double ans = 0;
cout << fixed << setprecision(9) << ans << "\n"; //保留9位
cout << setfill(' ') << setw(10) << ans << "\n"; //前面填充
for(int i = 0; i < 10; ++i) { //行末无空格
cout << i << " \n"[i == 9];
}
全排列
do {
} while (next_permutation(a.begin(), a.end()));
STL容器
将il数组赋初值从 base 开始每位等于前一位加一
iota(il.begin(), il.end(), base);
vector 向量数组
定义 vector
vector<int> ver1(n); // 开了一个容量为n的vector数组
vector<vector<int>> ver3(n); // 二维向量数组,开了n个vector数组
vector<vector<vector<int>>> ver4(n); // 三维向量数组
定义时赋值
vector<int> ver6(n, 1); // 将vector内的初值全部置为1
vector<vector<char>> ver7(n, vector<char>(m, 'a')); // 将二维数组内的值全部置为'a'
对vector进行操作
以下定义时必须保证数据类型相同
// 定义了一个长度为4, 类型为int,内容为{23, 3, 4, 56}数组
vector ver8 = {23, 3, 4, 56};
// 定义了一个长度为3, 类型为pair,内容为{pair{1, 3}, pair{2, 4}, pair{2, 3}}数组
vector ver9 = {pair{1, 3}, pair{2, 4}, pair{2, 3}};
vector ver10 = {vector{1, 2}, vector{23, 43}};
// 在ver8数组中插入n个vector{-1, -2, -3}的数组
vector<vector<int>> ver11(n, vector{-1, -2, -3});
在数组末尾插入一个数
v.push_back(20), v2.push_back({1, 1});
v.emplace_back(21), v2.emplace_back(2, 2); // 与上一个操作相同但速度更快
v.pop_back(); // 删除v数组中的最后一个元素, 时间复杂度O(1)
v.erase(v.begin()); // 删除v数组中的第一个元素,时间复杂O(n)
cout << "返回数组长度:" << v.size() << ' ' << size(v) << '\n';
cout << "返回数组第一个元素" << v[0] << ' ' << v.begin()[0] << "\n";
cout << "返回数组最后一个元素" << v.back() << ' ' << v.rbegin()[0] << "\n";
v.assign(3, 2); // 更改v数组的容量大小和初值,时间复杂度为O(n), 谨慎使用
vector<int> sv;
v.swap(sv); // 交换sv和v中的内容,时间复杂度为O(1)
v = sv; // 赋值操作,将v变为sv
v.insert(v.end(), v.begin(), v.end()); // 在v的末尾插入v数组, 时间复制O(n)
v.insert(v.begin() + 1, sv.end(), sv.end()); // 在v下班为1的位置开始插入sv数组
v.erase(v.begin() + 1, v.end()); // 将v数组从下标1开始全部删除,常用在去重
v.clear(); // 将v数组清空
函数操作
int k = 1;
vector a = {1, 3, 3, 8, 5, 6, 7, 7};
random_shuffle(a.begin(), a.end()); // 将数组随机打乱
sort(a.begin(), a.end()); // 对a数组从小到大进行排序
sort(a.rbegin(), a.rend()); // 对a数组从大到小进行排序
stable_sort(a.begin(), a.end()); // 稳定排序,复杂度O(nlogn);
a.erase(unique(a.begin(), a.end()), a.end()); // 对a数组进行去重,首先保证a数组有序
nth_element(a.begin(), a.begin() + k, a.end()); // 数组可以不有序, 查找数组中第k小的元素
iota(a.begin(), a.end(), 1); // 将a数组从1开始赋值
next_permutation(a.begin(), a.end()); // 将当前序列更换为全排列中的下一个序列
reverse(a.begin(), a.end()); // 将a数组倒置
vector 二分
二分查找,返回数组中第一个大于等于k的位置
int pos1 = lower_bound(a.begin(), a.end(), k) - a.begin();
二分查找,返回数组中第一个大于k的位置
int pos2 = upper_bound(a.begin(), a.end(), k) - a.begin();
二分查找,返回数组中第一个小于k的位置
int pos3 = (--lower_bound(a.begin(), a.end(), k)) - a.begin();
二分查找,返回数组中第一个小于等于k的位置
int pos4 = (--upper_bound(a.begin(), a.end(), k)) - a.begin();
返回a数组所有值的合, 其中0为初值,可以自行更改
int sum = accumulate(a.begin(), a.end(), 0LL);
string
定义string类型, 下标从0开始
int n = 10, start = 1, len = 2;
string str; // 定义一个空字符串
string str1 = "abcd"; // 定义一个字符串"abcd"
string str2(10, '1'); // 定义一个长度为n且每个字符都为’1‘的字符串
string str3(str2, start); // 定义一个字符串为str2[start: ], 即str3是从str2的下标start开始的字符拷贝
string str4(str2, start, len); // 定义一个字符串为str2[start: start + len], 即str4是从str2的下标start开始的len个字符的拷贝
对string进行操作
s[x] = 'b'; // 将x位置的值修改为'b'
s += 'c'; // 在s末尾插入一个字符'c'
s = 'a' + s; // 在s开头插入一个'a'
cout << "返回字符串长度: " << s.size() << ' ' << size(s) << '\n';
cout << "返回字符串第一个元素: " << s[0] << ' ' << s.begin()[0] << "\n";
cout << "返回字符串最后一个元素: " << s.back() << ' ' << s.rbegin()[0] << "\n";
cout << "获取字符串从x位置开始的p个字符: " << s.substr(x, p) << "\n";
cout << "获取字符串从x位置开始的所有字符: " << s.substr(x) << "\n";
// 字符串拼接,时间复杂度O(n), 将t插入到s的x位置:
cout << s.substr(0, x) + t + s.substr(x) << '\n';
// 字符串拼接,时间复杂度O(n), 字符串\"1111\"s的x位置:
cout << s.substr(0, x) + string(4, '1') + s.substr(x) << '\n';
s.erase(x); //从字符串s的第x + 1位开始删去所有字符
s.insert(x, t); //从字符串s的第x + 1位处插入字符串t
s.insert(x, p, u); // 从字符串的第x + 1位处连续插入p次字符u
s.replace(0, x, t); //将字符串s的0~x-1位替换成字符串t
s.replace(s.begin(), s.begin() + x, t); //将字符串s的0~x-1位替换成字符串t
// 在字符串s中查找字符串t, 如果找到返回首字母下标,否则返回string::npos, 即-1
int pos = s.find(t);
int rpos = s.rfind(t); // 与find相同,只不过查找的是t最后一次出现的位置
int poss = s.find(t, pos); // 在 s 中自 pos 位置起字符串 t 第一次出现的位置
s = t; // 使s = t
s.swap(t); // 交换s和t的内容
// cout << "判断t串是否为s串的前缀" << s.starts_with(t) << "\n";
// cout << "判断t串是否为s串的后缀" << s.ends_with(t) << "\n";
与字符操作有关的函数
transform(s.begin(), s.end(), s.begin(), ::tolower); // 将字符串全部转为小写
transform(s.begin(), s.end(), s.begin(), ::toupper); // 将字符串全部转为大写
char c = 'a';
cout << "检查字符c是否是字母和数字: " << isalnum(c) << "\n";
cout << "检查字符c是否是字母: " << isalpha(c) << "\n";
cout << "检查字符c是否是数字: " << isdigit(c) << "\n";
cout << "检查字符c是否是小写字母: " << islower(c) << "\n";
cout << "检查字符c是否是大写字母: " << isupper(c) << "\n";
cout << "检查字符c是否是标点符号: " << ispunct(c) << "\n";
int w = 1233;
string sw = "1233", s16 = "FAB", sx = "12A";
cout << "将数字转换为字符串" << to_string(w) << '\n';
cout << "将字符串转化为int类型" << stoi(sw) << '\n';
cout << "将字符串转化为long long类型" << stoll(sw) << '\n';
cout << "将字符串转化为double类型" << stod(sw) << '\n';
cout << "将十六进制字符串转化为long long类型" << stoll(s16, nullptr, 16) << '\n';
x = 12;
cout << "将x进制字符串转化为long long类型" << stoll(sx, nullptr, x) << '\n';
以下操作和用法同vector一样,不再过多赘述,可以将string看成vector
// 将数组随机打乱
random_shuffle(a.begin(), a.end());
// 对a数组从小到大进行排序
sort(a.begin(), a.end());
// 对a数组从大到小进行排序
sort(a.rbegin(), a.rend());
// 稳定排序,复杂度O(nlogn);
stable_sort(a.begin(), a.end());
// 对a数组进行去重,首先保证a数组有序
a.erase(unique(a.begin(), a.end()), a.end());
// 数组可以不有序, 查找数组中第k大的元素
nth_element(a.begin(), a.begin() + k, a.end());
// 将a数组从1开始赋值
iota(a.begin(), a.end(), 1);
// 将当前序列更换为全排列中的下一个序列
next_permutation(a.begin(), a.end());
// 将a数组倒置
reverse(a.begin(), a.end());
// 二分查找,返回数组中第一个大于等于k的位置
int pos1 = lower_bound(a.begin(), a.end(), k) - a.begin();
// 二分查找,返回数组中第一个大于k的位置
int pos2 = upper_bound(a.begin(), a.end(), k) - a.begin();
// 二分查找,返回数组中第一个小于k的位置
int pos3 = (--lower_bound(a.begin(), a.end(), k)) - a.begin();
// 二分查找,返回数组中第一个小于于等于k的位置
int pos4 = (--upper_bound(a.begin(), a.end(), k)) - a.begin();
// 顺序查找,返回第一个为k的值的位置
int pos5 = find(a.begin(), a.end(), k) - a.begin();
// 返回a数组中最大值的位置
int maxa = max_element(a.begin(), a.end()) - a.begin();
// 返回a数组中最小值的位置
int mina = min_element(a.begin(), a.end()) - a.begin();
// 返回数组中的最大值和最小值
cout << ranges::max(a) << " " << ranges::min(a) << '\n';
// 返回a数组所有值的合, 其中0为初值,可以自行更改
int sum = accumulate(a.begin(), a.end(), 0LL);
map
定义,与set一样,内部升序,键值唯一
constexpr int N = 11;
map<int, int> mp1; // 变量映射变量
map<int, vector<int>> mp2; // 变量映射STL容器,不可以是数组
map<map<int, int>, int> mp3; // STL容器映射变量
map<set<int>, vector<int>> mp4; // STL容器映射STL容器
map<string, int, greater<>> mp5; // 定义一个内部降序的映射
vector<map<int, pair<int, int>>> mp6(N); // 定义N个map
map<pair<int, int>, int> mp7[N]; // 定义N个map, 全局
map<int, string> mp8{{1, "a"}, {2, "b"}}; // 定义时初始化
修改, O(logn),操作与set类似,获取到的键值为pair类型
map<int, string> mp;
mp[0] = "STL", mp[1] = "ABC"; // 添加
mp[0] = "pp"; // 修改
mp.erase(mp.begin()); // 删除第一个元素
mp.erase(1); // 删除键为1的元素
mp.clear(); // 清楚map中的元素
for (int i = 3; i <= N; ++i) { mp[i] = to_string(i * i); }
map<int, string> cmp = mp; // 使cmp等于mp
swap(cmp, mp); // 交换cmp和mp
获取 O(logn)
int x = 0;
mp[0] = "STL";
cout << "输出map中有多少个键值对" << mp.size() << '\n';
cout << "判断map中x键有多少个" << mp.count(x) << '\n';
cout << "判断map中x键是否存在" << mp.contains(x) << '\n'; // 返回bool类型
auto [a, b] = *mp.begin(); // 获取map中第一个元素的键和值
int aa = mp.begin()->first; // 获取map中第一个元素的键
string bb = mp.begin()->second; // 获取map中第一个元素的值
auto pp = --mp.end();
auto pre = prev(pp); // 获取当前地址的前一个地址,不改变当前地址
pp--; //地址前移,改变当前地址
auto ne = next(pp); // 获取当前地址的后一个地址,不改变当前地址
pp++; //地址后移,改变当前地址
遍历
for (auto [c, str] : mp) { cout << c << ' ' << str << '\n'; }
for (auto v : mp) { cout << v.first << ' ' << v.second << '\n'; }
for (auto it = mp.begin(); it != mp.end(); ++it) {
cout << it->first << ' ' << it->second << '\n';
cout << (*it).first << ' ' << (*it).first << '\n';
}
// lower_bound, upper_bound 函数与set相同,map中查找的为键,这里不重打一遍了,参照set即可
// 判断时的注意事项
// 如果键111不存在,则会自动创建一个键{111, ""}, 这样判断会增加时间复杂度, 建议采用以下方法判断键值是否存在
if (mp[111] != "HT") {}
if (mp.contains(111)) {} //
if (mp.count(111)) {} //
// 获取两个元素之间的距离,时间复杂度O(n),使用算一下时间复杂度
cout << distance(mp.begin(), mp.end()) << '\n';
pair
定义
// 简化版结构体,即可以将不同的数据类型绑定在一起pair<first, second>
pair<int, double> pid; // 绑定变量
pair<int, int> pii{1, 1}; // 初始化值
pair<set<int>, vector<int>> psv; // 绑定STL容器,默认为空
pair<int, pair<int, string>> piis; // 绑定pair自身
调用
int n = 10;
vector<pair<int, int>> a(n);
for (int i = 0; i < n; ++i) { // 赋值
a[i] = pair{i * i, i * i}; // 统一修改
a[i].first = i * i; // .first即修改pair的第一个健值
a[i].second = i * i; // .first即修改pair的第一个健值
}
// 末尾插入操作格式
a.push_back({-1, -1}), a.emplace_back(-1, -1);
// 默认按第一个键值为关键词进行升序排序
sort(a.begin(), a.end());
// 二分查找a数组中第一个键值大于1的位置
int pos = lower_bound(a.begin(), a.end(), pair{1, -1}) - a.begin();
pair<int, pair<int, int>> piii{1, {-1, -1}};
piii.second.first = 0; // 将piii的第二个健值的第一个键值设为0
// 解包,用两个变量将pair中的值取出来
pair<int, int> s{4, 5};
int x, y;
tie(x, y) = s;
auto [xx, yy] = s;
cout << x << ' ' << y << '\n';
cout << xx << ' ' << yy << '\n';
tuple 元组
vector<tuple<int, int>>取法
int a,b;
vector<tuple<int,int>>tl;
for(int i=0; i<7; ++i) { cin>>a>>b; tl.emplace_back(a+b,-(i+1)); }
sort(tl.begin(),tl.end());
if (get<0>(tl[6]) > 8) { cout<<-get<1>(tl[6])<<"\n"; }
else { cout << 0 << "\n"; }
针对 tuple
// 定义
// 可以看成pair的推广,与pair不同的是可以绑定更多的异类值
tuple<int, int, int> tiii;
tuple<int, string, vector<int>, int> tisvi;
// 调用
tuple<int, int, int> tup{1, 2, 4};
cout << "输出tup元组的第一个值: " << get<0>(tup) << "\n";
cout << "输出tup元组的所有值: \n";
for (int i = 0; i < 3; ++i) { cout << get<0>(tup) << " \n"[i == 2]; }
// 其余操作与pair同理,不再过多赘述
vector<tuple<int , int , int>> tl;
int n = 5;
for (int i = 0; i < n; ++ i) {
int a , b , c;
cin >> a >> b >> c;
tl.emplace_back(a , b , c);
}
int len = tl.size();
for (int i = 0; i < len; ++ i) {
int a = get<0>(tl[i]);
int b = get<1>(tl[i]);
}
for (auto [a, b, c] : tl) {cout << a<<" " << b<<" " << c<<"\n";}
set
定义,内部默认单调升序
constexpr int N = 11;
set<int> st1; // 定义一个int类型的集合
set<vector<int>> st2; // 定义一个vector<int>类型的集合
set<pair<int, int>> st3; // 定义一个pair<int, int>类型的集合
set<int> st4{{1, 2, 2, 4}}; // 定义一个st4, 并插入元素1, 2, 2, 4
vector a = {1, 2, 2, 4};
set st5 = set{a}; // 定义一个st5, 将数组a中的元素全部放入set
vector<set<int>> st6(N); // 定义了N个set
set<int> st7[N]; // 定义了N个set,这么定义建议在全局定义
set<int, greater<>> st8; // 定义一个单调降序的set
插入操作
set<int> sti;
set<pair<int, int>> stp;
sti.insert(1), sti.emplace(1); // 插入一个元素
stp.insert({1, 2}), stp.emplace(1, 2); // 插入一个元素
sti.insert({1, 2, 3, 4, 5, 7}); // 一次插入多个元素
stp.insert({{1, 2}, {3, 4}, {5, 7}}); // 一次插入多个元素
获取值
auto posi1 = sti.begin(); // 获取sti中第一个元素的地址
int vali1 = *posi1; // 获取sti中第一个元素的值
auto posi2 = sti.rbegin();
auto posi3 = --sti.end();
auto posi4 = prev(sti.end()); // 获取sti中最后一个元素的地址
int vali2 = *posi2; // 获取sti中最后一个元素的值
auto posp1 = stp.begin(); // 获取stp中第一个元素的地址
auto valp1 = *posp1; // 获取stp中第一个元素的值, 类型为pair<int, int>
auto [x, y] = *posp1;
int xx = posp1->first, yy = posp1->second; // 将pair中的两个值取出
cout << x << ' ' << y << '\n';
cout << xx << ' ' << yy << '\n';
auto pp = --stp.end();
auto pre = prev(pp); // 获取当前地址的前一个地址,不改变当前地址
pp--; //地址前移,改变当前地址
auto ne = next(pp); // 获取当前地址的后一个地址,不改变当前地址
pp++; //地址后移,改变当前地址
删除
int n = 10;
set<int> st;
for (int i = 0; i < n; ++i) {
st.insert(i);
}
st.erase(st.begin()); // 删除第一个元素
st.erase(--st.end()); // 删除最后一个元素
st.erase(1); // 将1这个值删除
st.clear(); // 清空集合
// 遍历set, 集合元素中的值无法被修改
for (auto x : st) {
cout << x << ' ';
}
cout << '\n';
for (auto it = st.begin(); it != st.end(); ++it) {
cout << (*it) << "\n";
}
cout << '\n';
查询操作
int query = 20;
cout << st.count(query) << "\n"; // 查寻st集合中query元素数量
// cout << st.contains(query) << "\n"; // 查寻st集合中query元素是否存在,返回类型为bool
cout << st.size() << "\n"; // 返回集合大小
cout << st.empty() << '\n'; // 判断集合和是否为空
二分查找
constexpr int INF = 1e9;
st.insert({-INF, INF}); // 未插入边界时,使用||前面的方法,插入边界后可以使用||后面的方法
auto pos1 = st.lower_bound(query); // 查找集合中大于等于query的元素的地址
auto pos2 = st.upper_bound(query); // 查找集合中大于query的元素的地址
auto pos3 = --st.lower_bound(query); // 查找集合中小于query的元素的地址
auto pos4 = --st.upper_bound(query); // 查找集合中小于等于query的元素的地址
// 判断边界,
// !!!注意如果要进行删除操作,优先将pos为位置的值存下再删除,否则先删除后再取不是原来的值
if (pos1 == st.end() || *pos1 == INF) { cout << "st中不存在大于等于query的元素" << '\n'; }
if (pos2 == st.end() || *pos2 == INF) { cout << "st中不存在大于query的元素" << '\n'; }
if (query <= *st.begin() || *pos3 == -INF) { cout << "st中不存在小于query的元素" << '\n'; }
if (query < *st.begin() || *pos4 == -INF) { cout << "st中不存在小于等于query的元素" << '\n'; }
// eg.将st中的2改成5,只能通过先删除再添加的方式修改
st.erase(2);
st.insert(5);
// 更改操作
set<int> cst;
st = cst; // 将cst复制给st
st.swap(cst); // 交换cst和st
// 获取两个元素之间的距离,时间复杂度O(n),使用算一下时间复杂度
cout << distance(st.begin(), st.end()) << '\n';
queue
定义
queue<int> que; // 定义一个名为que的队列
queue<int> q{{1, 2, 3, 4}}; // 在q队列中插入初值
// 插入操作
que.push(1), que.emplace(2); // 单个插入
// 查询
cout << "返回队头元素: " << q.front() << "\n";
cout << "查询队内元素个数: " << q.size() << '\n';
cout << "查询队列是否为空: " << q.empty() << "\n"; // 返回bool类型
// 出队
q.pop(); // 将队头元素删除
// 清空队列
// 方法一: 单个出队,直至队列为空
while (q.size()) {
q.pop();
}
// 方法二:定义一个空队列,使其覆盖原队列
queue<int> cq;
q = cq;
优先队列
priority_queue<ll, vector<ll>, greater<ll>> q; // 升序
priority_queue<ll, vector<ll>> q; // 降序
deque
定义
deque<int> que; // 定义一个名为que的双端队列
deque<int> q{{1, 2, 3, 4}}; // 在q队列中插入初值
// 插入操作
que.push_front(1), que.emplace_front(2); // 单个队头插入
que.push_back(1), que.emplace_back(2); // 单个队尾插入
// 查询
cout << "返回队头元素: " << q.front() << "\n";
cout << "返回队尾元素: " << q.back() << "\n";
cout << "查询队内元素个数: " << q.size() << '\n';
cout << "查询队列是否为空: " << q.empty() << "\n"; // 返回bool类型
// 出队
q.pop_front(); // 将队头元素删除
q.pop_back(); // 将队尾元素删除
// 清空队列
// 方法一: 单个出队,直至队列为空
while (q.size()) {
q.pop_back();
}
// 方法二:定义一个空队列,使其覆盖原队列
deque<int> cq;
q = cq;
stack
定义
stack<int> stk; // 定义一个名为stk的队列
stack<int> s{{1, 2, 3, 4}}; // 在s栈中插入初值
// 插入操作
stk.push(1), stk.emplace(2); // 单个插入
// 查询
cout << "返回栈头元素: " << s.top() << "\n";
cout << "查询栈内元素个数: " << s.size() << '\n';
cout << "查询栈是否为空: " << s.empty() << "\n"; // 返回bool类型
// 出栈
s.pop(); // 将栈顶元素删除
// 清空栈
// 方法一: 单个出栈,直至栈为空
while (s.size()) {
s.pop();
}
// 方法二:定义一个空栈,使其覆盖原栈
stack<int> cs;
s = cs;
bitset
bitset 定义
constexpr int N = 22, num = 15;
const string str = "101010111";
bitset<N> bit1;// 指定大小时必须为常量
vector<bitset<N>> bit2(N); // 定义N个长度为N的bitset
bitset<N> bit3(num); // 将十进制数num放入bit3容器转换为二进制数
bitset<N> bit4(str); // 将01字符串str放入bit4容器转换为二进制数
// 运算符
// operator<> : 流运算符,这意味着你可以通过 cin / cout 进行输入输出
bitset<N> bit(31);
cin >> bit;
cout << bit << '\n';
// operator []: 访问其特定的一位。
for (int i = 0; i < N; ++i) { cout << bit[i]; } cout << '\n';
// operator== / != : 比较两个 bitset 内容是否完全一样。
cout << (bit == bit1) << " " << (bit != bit1) << '\n';
// operator &/&=/|/| =/^/^=/~: 进行按位与/或/异或/取反操作。
// bitset 只能与 bitset 进行位运算,若要和整型进行位运算,要先将整型转换为 bitset。
cout << (bit & bit1) << "\n";
cout << (bit | bit1) << "\n";
cout << (bit ^ bit1) << "\n";
cout << (~bit) << "\n";
bit &= bit1;
cout << "按位与: " << bit << '\n';
bit |= bit1;
cout << "按位或: " << bit << '\n';
bit ^= bit1;
cout << "按位异或: " << bit << '\n';
// operator <>/<<=/>>=: 进行二进制左移/右移。
cout << "左移: " << (bit << 1) << " 右移: " << (bit >> 1) << '\n';
bit >>= 1; cout << bit << "\n";
bit <<= 1; cout << bit << "\n";
成员函数
int pos = 1;
bool flag = false;
cout << "返回true的数量" << bit.count() << "\n";
cout << "返回bitset的大小" << bit.size() << '\n';
cout << "查询pos位置的值" << bit.test(pos) << '\n';
cout << "查询是否存在true" << bit.any() << '\n';
cout << "查询是否全为false" << bit.none() << '\n';
cout << "查询是否全为true" << bit.all() << '\n';
bit.set(); // 将bit全部设置为true
bit.set(pos, flag); // 将pos位置设置为flag
bit.reset(); // 将bit全部设置为false
bit.reset(pos); // 将pos位置设置为false
bit.flip(); // 翻转每一位
bit.flip(pos); // 翻转pos上的数
string sbit = bit.to_string(); // 转换成的字符串表达
LL ubit = bit.to_ullong(); // 转换成unsigned long long类型
// 返回 bitset 第一个 true 的下标,若没有 true 则返回 bitset 的大小
int fpos = bit._Find_first();
// 返回 pos 后面(下标严格大于 pos 的位置)第一个 true 的下标,
// 若 pos 后面没有 true 则返回 bitset 的大小
int npos = bit._Find_next(pos);
// 第k位数,下面三种等效
int n=5,k=1,x=0;
n>>k&1;
// lowbit(x);
x&(-1);
x&(~x+1);
算法基础
前缀和 & 差分
一维前缀和
auto prefix = [&](vector<ll> &a, int lena) {
vector<ll> sa(lena + 1);
for (int i = 1; i <= lena; ++ i) {
sa[i] = sa[i - 1] + a[i];
}
return sa;
};
auto get = [&](vector<ll> &sa, int xb, int xe) {
ll res = sa[xe] - sa[xb - 1];
return res;
};
一维差分
auto dif = [&](vector<ll> &a, int lena) {
vector<ll> da(lena + 1);
for (int i = 1; i <= lena; ++ i) {
da[i] = a[i] - a[i - 1];
}
return da;
};
auto add = [&](vector<ll> &da, int s, int t, ll n) {
da[s] += n;
da[t + 1] -= n;
};
二维前缀和
auto prefix = [&](vector<vector<ll>> &a, int lena, int lenai) {
vector<vector<ll>> sa(lena + 1 , vector<ll>(lenai + 1));
for (int i = 1; i <= lena; ++ i) {
for (int j = 1; j <= lenai; ++ j) {
sa[i][j] = a[i][j] + sa[i - 1][j] + sa[i][j - 1] - sa[i - 1][j - 1];
}
}
return sa;
};
auto get = [&](vector<vector<ll>> &sa, int xb, int yb, int xe, int ye) {
ll res = sa[xe][ye] - sa[xe][yb - 1] - sa[xb - 1][ye] + sa[xb - 1][yb - 1];
return res;
};
二维差分
auto dif = [&](vector<vector<ll>> &a, int lena, int lenai) {
vector<vector<ll>> da(lena + 1 , vector<ll>(lenai + 1));
for (int i = 1; i <= lena; ++ i) {
for (int j = 1; j <= lenai; ++ j) {
da[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
}
}
return da;
};
auto add = [&](vector<vector<ll>> &da, int si, int sj, int ti, int tj, ll x) {
int n = da.size(), m = da[si].size();
da[si][sj] += x;
if (tj + 1 < m) da[si][tj + 1] -= x;
if (ti + 1 < n) da[ti + 1][sj] -= x;
if (ti + 1 < n && tj + 1 < m)da[ti + 1][tj + 1] += x;
};
二分
ll l = 1, r = inf;
while (l <= r) {
ll mid = (l + r) >> 1;
if (check(mid)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
三分
ll n, b;
cin >> n >> b;
ll x = 0;
auto f = [&](ll x) {
ll left = (b + b + x - 1ll) * x / 2ll;
ll right = (b + b + n - 1ll) * n / 2ll - left;
return abs(left - right);
};
ll l = 0, r = n;
ll d1 = inf, d2 = inf;
while (l <= r) {
ll lm = l + (r - l) / 3;
ll rm = r - (r - l) / 3;
d1 = f(lm);
d2 = f(rm);
if (d1 <= d2) {
r = rm - 1;
} else {
l = lm + 1;
}
}
ll ans = min(d1, d2);
cout << ans << "\n";
struct BigInt {
std::vector<char> v;
BigInt(int x = 0) : v{} {
do v.push_back(x % 10); while (x /= 10);
}
BigInt(std::string &x) : v{} {
assert(size(x) != 0);
for (int i = (int)size(x) - 1; i >= 0; --i) {
v.push_back(x[i] - '0');
}
}
BigInt &operator=(int x) {
v.clear();
do v.push_back(x % 10); while (x /= 10);
return *this;
}
BigInt &operator=(const BigInt &x) {
v.resize(x.v.size());
memcpy(const_cast<char *>(v.data()), x.v.data(), x.v.size() * sizeof(char));
return *this;
}
friend bool operator==(const BigInt &a, const BigInt &b) {
if (size(a.v) == size(b.v)) {
int idx = 0;
while (idx < (int)size(a.v) && a.v[idx] == b.v[idx]) idx++;
return idx < (int)size(a.v);
} else {
return false;
}
}
friend bool operator!=(const BigInt &a, const BigInt &b) {
return !(a == b);
}
friend bool operator<(const BigInt &a, const BigInt &b) {
if (size(a.v) == size(b.v)) {
int idx = size(a.v) - 1;
while (idx >= 0 && a.v[idx] == b.v[idx]) idx--;
return idx >= 0 && a.v[idx] < b.v[idx];
}
return size(a.v) < size(b.v);
}
friend bool operator<=(const BigInt &a, const BigInt &b) {
if (size(a.v) == size(b.v)) {
int idx = size(a.v) - 1;
while (idx >= 0 && a.v[idx] == b.v[idx]) idx--;
return idx == -1 || a.v[idx] <= b.v[idx];
}
return size(a.v) < size(b.v);
}
friend bool operator>(const BigInt &a, const BigInt &b) {
if (size(a.v) == size(b.v)) {
int idx = size(a.v) - 1;
while (idx >= 0 && a.v[idx] == b.v[idx]) idx--;
return idx >= 0 && a.v[idx] > b.v[idx];
}
return size(a.v) > size(b.v);
}
friend bool operator>=(const BigInt &a, const BigInt &b) {
if (size(a.v) == size(b.v)) {
int idx = size(a.v);
while (idx >= 0 && a.v[idx] == b.v[idx]) idx--;
return idx == -1 || a.v[idx] >= b.v[idx];
}
return size(a.v) > size(b.v);
}
BigInt &operator+=(const BigInt &x) & {
int n = std::max<int>(size(v), size(x.v)), tmp = 0;
bool flag = false;
for (int i = 0; i < n; ++i) {
if (i >= (int)size(v)) v.push_back(0);
if (i < (int)size(x.v)) v[i] += x.v[i];
if (flag) v[i] += 1, flag = false;
if (v[i] >= 10) v[i] %= 10, flag = true;
}
if (flag) v.push_back(1);
return *this;
}
BigInt &operator-=(const BigInt &x) & {
assert(*this >= x);
bool flag = false;
for (int i = 0; i < (int)size(v); ++i) {
if (i < (int)size(x.v)) v[i] -= x.v[i];
if (flag) v[i] -= 1, flag = false;
if (v[i] < 0) v[i] += 10, flag = true;
}
while (size(v) > 1 && v.back() == 0) v.pop_back();
return *this;
}
BigInt &operator*=(const int &x) & {
int tmp = 0;
for (int i = 0; i < size(v); ++i) {
tmp += (int)x * v[i];
v[i] = tmp % 10;
tmp /= 10;
}
while (tmp) {
v.push_back(tmp % 10);
tmp /= 10;
}
return *this;
}
BigInt &operator*=(const BigInt &x) & {
BigInt result;
result.v.resize(size(v) + size(x.v));
for (int i = 0; i < (int)size(v); ++i) {
for (int j = 0; j < (int)size(x.v); ++j) {
result.v[i + j] += v[i] * x.v[j];
result.v[i + j + 1] += result.v[i + j] / 10;
result.v[i + j] %= 10;
}
}
while (size(result.v) > 1 && result.v.back() == 0) result.v.pop_back();
return *this = result;
}
BigInt &operator/=(const int &x) & {
int r = 0;
for (int i = size(v) - 1; i >= 0; --i) {
r = r * 10 + v[i];
v[i] = r / x;
r %= x;
}
while (size(v) > 1 && v.back() == 0) v.pop_back();
return *this;
}
int operator%=(const int &x) {
int r = 0;
for (int i = size(v) - 1; i >= 0; --i) {
r = r * 10 + v[i];
r %= x;
}
return r;
}
friend BigInt operator+(BigInt a, const BigInt &b) {
return a += b;
}
friend BigInt operator-(BigInt a, const BigInt &b) {
return a -= b;
}
friend BigInt operator*(BigInt a, const int &b) {
return a *= b;
}
friend BigInt operator*(BigInt a, const BigInt &b) {
return a *= b;
}
friend BigInt operator/(BigInt a, const int &b) {
return a /= b;
}
friend int operator%(BigInt a, const int &b) {
return a %= b;
}
friend std::istream &operator>>(std::istream &is, BigInt &a) {
std::string str;
is >> str;
a = BigInt(str);
return is;
}
friend std::ostream &operator<<(std::ostream &os, const BigInt &a) {
for (int i = a.v.size() - 1; i >= 0; --i) os << (char)(a.v[i] + '0');
return os;
}
};
搜索
BFS
Breadth First Search 广度优先搜索
每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。
class Point { // 点类,重载了点的加减法和输入输出,并且可以直接sort
public:
int x, y;
Point () {}
Point (int x, int y) : x(x), y(y) {}
// 重载见计算几何
};
int main() {
vector<Point> d = {{-1,0},{1,0},{0,-1},{0,1}};
int n, m; cin >> n >> m;
Point be, ed;
vector<vector<int>> g(n + 1, vector<int>(m + 1, 1));
vector<vector<int>> dp(n + 1, vector<int>(m + 1)); // dp[i][j]等于1表示该点能到
auto check = [&](Point p) { };// 用于判断点是否在范围内
auto bfs = [&](Point be) {
queue<Point> q; dp[be.x][be.y] = 1; q.push(be);
while (q.size()) {
auto t = q.front(); q.pop();
for (int i = 0; i < 4; i ++ ) {
Point p = t + d[i];
if (check(p) && g[p.x][p.y] && dp[p.x][p.y] == 0)
dp[p.x][p.y] = 1; q.push(p);
}
}
};
bfs(be);
}
动态规划(DP)
动态规划基础
最大子段和 mss
auto mss = [&](vector<ll> &il, int l, int r) { // O(n)
ll res = -1e9, sum = 0;
for (int i=l; i<r; ++i) {
if (sum < 0) {
sum = il[i];
} else {
sum += il[i];
}
res = max(sum , res);
}
return res;
};
最长上升子序列 lis
auto lis =[&](vector<int> &il) { // O(nlogn)
int fn = il.size();
vector<int> q(fn);
int len = 0;
q[0] = -2e9;
for (int i = 0; i < fn; ++i) {
int l = 0, r = len ;
while (l <= r) {
int mid = (l + r) >> 1;
if (q[mid] < il[i]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
len = max(len , l);
q[l] = il[i];
}
return len;
};
最长公共子序列 lcs
auto lcs = [&](string &a , string &b) { // O(n**2)
int lena = a.size();
int lenb = b.size();
vector<vector<int>> f(lena + 1 , vector<int>(lenb + 1));
vector<vector<int>> pre(lena + 1 , vector<int>(lenb + 1));
for (int i = 0; i < lena; ++ i) {
for (int j = 0; j < lenb; ++ j) {
if (a[i] == b[j]) {
f[i + 1][j + 1] = f[i][j] + 1;
pre[i + 1][j + 1] = 1;
} else {
if (f[i + 1][j] > f[i][j + 1]) { // left
pre[i + 1][j + 1] = 2;
} else { // up
pre[i + 1][j + 1] = 3;
}
f[i + 1][j + 1] = max(f[i + 1][j] , f[i][j + 1]);
}
}
}
// return f[lena][lenb];
int nans = f[lena][lenb];
string sans = "";
int i = lena , j = lenb;
while (i > 0 && j > 0) {
if (pre[i][j] == 1) {
sans = a[i - 1] + sans;
i--;
j--;
} else if (pre[i][j] == 2) {
j--;
} else if (pre[i][j] == 3) {
i--;
}
}
return sans;
};
树形DP
通过 dfs,在返回上一层时更新当前结点的最优解。
vector<ll> w(n + 1); // w[i] 表示节点 i 的 点权
vector<ll> dp(n + 1 , N); // dp[i] 表示当前节点所能达到的 最大值
vector<vector<int>> adj(n + 1 , vector<int>(0)); // 邻接表
auto transition = [&](int child, int father) {
if (dp[child] > w[father]) {
dp[father] = min(dp[father], (w[father] + dp[child]) / 2ll);
} else {
dp[father] = min(dp[father], dp[child]);
}
};
auto dfs = [&](auto self , int cur , int father) -> void {
for (auto child : adj[cur]) {
if (child == father) continue; // 防止往回遍历
self(self , child , cur);
transition(child, cur);
}
};
dfs(dfs , 1 , -1);
字符串
字符串哈希
constexpr ll P = 131; // 基底,即多项式哈希的进制数
constexpr int lem = 2; // 多值哈希的模数个数
class Hash { public: vector<ll> n = vector<ll> (lem + 1); };
int main() {
vector<ll> mod = {0ll, (ll)1e9 + 7, (ll)1e9 + 9}; // 模数
vector<vector<ll>> p(lent + 1, vector<ll>(lem + 1, 1)); // 位权
for (int i = 1; i <= lent; ++ i)
for (int j = 1; j <= lem; ++ j)
p[i][j] = (p[i - 1][j] * P) % mod[j];
auto hashing = [&](string a, int len) { // 哈希化
vector<Hash> h(len + 1);
for (int i = 1; i <= len; ++ i)
for (int j = 1; j <= lem; ++ j)
h[i].n[j] = (h[i - 1].n[j] * P + a[i - 1]) % mod[j];
return h;
};
auto get = [&](const vector<Hash> &h , int l, int r) { // 得到哈希值,注意是闭区间
Hash res;
for (int j = 1; j <= lem; ++ j) {
res.n[j] = (h[r].n[j] - h[l - 1].n[j] * p[r - l + 1][j] % mod[j] + mod[j]) % mod[j];
}
return res;
};
auto equal = [&](Hash h1, Hash h2) { // 判断两个哈希值是否相等
for (int j = 1; j <= lem; ++ j) { if (h1.n[j] != h2.n[j]) return 0; }
return 1;
};
}
数学
位运算
异或
异或及其性质
异或在C++中的运算符是 ^ (Shift + 6)
异或可以理解为按位不进位加法
1.异或的逆运算就是异或本身 如果 a $ \otimes $ b = c ,那么 c $ \otimes $ b = a
2.异或满足交换律 即 a $ \otimes $ b == b $ \otimes $ a
3.异或满足结合律 即 (a $ \otimes $ b) $ \otimes $ c == a $ \otimes $ (b $ \otimes $ c)
4.异或满足分配律 即 a $ \otimes $ (b & c) == (a $ \otimes $ b) & (a $ \otimes $ c)
对于普通加法可以用高斯定律 sn = (1 + n) * n / 2 快速计算1~n的值
对于异或运算来说也有快速计算1~n各数的异或和的方法,即:
异或前缀和
\( s(n)为1到n的数的异或和 \)
\( s(n) = \begin{cases} 1 , ~~~ n \% 4 == 1 \\ 0 , ~~~ n \% 4 == 3 \\ n , ~~~ n \% 4 == 0 \\ n + 1 , ~~~ n \% 4 == 2 \\ \end{cases} \)
代码实现如下:
auto xorprefix = [&](ll n) {
int flag = n % 4;
if (flag == 0) {
return n;
} else if (flag == 1) {
return 1;
} else if (flag == 2) {
return n + 1;
} else if (flag == 3) {
return 0;
}
};
快速幂
auto power (ll a, ll b, ll P) {
ll res = 1;
while (b) {
if (b & 1) {
res = (ll)res * a % P;
}
a = (ll)a * a % P;
b >>= 1;
}
return res;
}
数论
简化公式
\( \sum\limits_{k = 1}^{n} k = \frac{n(n + 1)}{2} \)
\( \sum\limits_{k = 1}^{n} k^{2} = \frac{n(n + 1)(n + 2)}{6} \)
\( \sum\limits_{k = 1}^{n} k^{3} = (\sum\limits_{k = 1}^{n} k)^{2} = \frac{ n^{2} (n + 1)^{2} }{4} \)
\( \sum\limits_{k = 1}^{n} k^{4} = \frac{n(n+1)(2n+1)(3n^2+3n-1)}{30} \)
数论基础
整除
\( a \vert b ,表示a是b的因子,即b可被a整除 \)
积性函数
定义
\( 若函数f(n), 满足f(1) = 1, 且 \forall x, y \in N^{*}, gcd(x, y) = 1 都有 f(xy) = f(x)f(y),则 f(n)\)为积性函数
\( 若函数f(n), 满足f(1) = 1, 且 \forall x, y \in N^{*} 都有 f(xy) = f(x)f(y),则 f(n)\)为完全积性函数
性质
\( 若 f(x) 和 g(x) 均为积性函数,则以下函数也为积性函数: \)
\( h(x) = f(x^{p}) \)
\( h(x) = f^{p}(x) \)
\( h(x) = f(x)g(x) \)
\( h(x) = \sum\limits_{d \vert x} f(d)g(\frac{x}{d}) \)
质数
Miller–Rabin 素性测试
二次探测定理
\( 如果 p 是奇质数,则 x^{2} \equiv 1 ~ (mod ~ p) 的解为 x \equiv 1 ~ (mod ~ p)或者 x \equiv p - 1 ~ (mod ~ p) \)
bool Miller_Rabin(long long p) { // 判断素数
if (p < 2) return 0;
if (p == 2) return 1;
if (p == 3) return 1;
long long d = p - 1, r = 0;
while (!(d & 1)) ++r, d >>= 1; // 将d处理为奇数
for (long long k = 0; k < 10; ++k) {
long long a = rand() % (p - 2) + 2;
long long x = power(a, d, p);
if (x == 1 || x == p - 1) continue;
for (int i = 0; i < r - 1; ++i) {
x = (__int128)x * x % p;
if (x == p - 1) break;
}
if (x != p - 1) return 0;
}
return 1;
}
long long Pollard_Rho(long long x) {
long long s = 0, t = 0;
long long c = (long long)rand() % (x - 1) + 1;
int step = 0, goal = 1;
long long val = 1;
for (goal = 1;; goal *= 2, s = t, val = 1) { // 倍增优化
for (step = 1; step <= goal; ++step) {
t = ((__int128)t * t + c) % x;
val = (__int128)val * abs(t - s) % x;
if ((step % 127) == 0) {
long long d = gcd(val, x);
if (d > 1) return d;
}
}
long long d = gcd(val, x);
if (d > 1) return d;
}
}
求最大质因子
ll max_factor = 0;
void fac(ll x) {
if (x <= max_factor || x < 2) return;
if (Miller_Rabin(x)) { // 如果x为质数
max_factor = max(max_factor, x); // 更新答案
return;
}
long long p = x;
while (p >= x) p = Pollard_Rho(x); // 使用该算法
while ((x % p) == 0) x /= p;
fac(x), fac(p); // 继续向下分解x和p
}
求所有质因子
std::vector<long long> factor(long long n) {
if (n == 1) return {};
std::vector<long long> g, d;
d.push_back(n);
while (!d.empty()) {
auto v = d.back();
d.pop_back();
auto rho = pollard_rho(v);
if (rho == v) {
g.push_back(rho);
} else {
d.push_back(rho);
d.push_back(v / rho);
}
}
std::sort(g.begin(), g.end());
return g;
}
最大公约数
扩展欧几里得算法
扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求 ax+by=gcd(a,b) 的一组可行解。
ll exgcd(ll a , ll b , ll &x , ll &y) {
if (b == 0) {
x = 1, y = 0;
return a;
} else {
ll d = exgcd(b , b%a , x , y);
ll t = x;
x = y;
y = t - a / b * y1;
return d;
}
}
欧拉函数
定义
欧拉函数,即 $ \phi(n) $,表示的是小于等于n的和n互质的数的个数。
性质
- $ 欧拉函数,是积性函数。$
$ 即对任意满足gcd(a,b) == 1的整数a,b,有 \phi(ab) = \phi(a)\phi(b) $
$ 特别的,当n是奇数时 \phi(2n) = \phi(n) $ - $ n = \sum\limits_{d \vert n} \phi(d)$
- \(若 n = p^{k},其中p是质数,那么 \phi(n) = p^{k} - p^{k-1}\)
- $ 由唯一分解定理,设 n = \prod\limits_{i = 1}^{s} (p_{i}^{k_{i}}) ,其中p_{i}是质数,有 \phi(i) = n * \prod\limits_{i = 1}^{s} \frac{p_{i} - 1}{p_{i}}$
- $ 对任意不全为0的整数m,n,\phi(mn)\phi(gcd(m, n)) = \phi(m)\phi(n)\phi(gcd(m, n)) $
ll phi(ll n) {
ll ans = n;
for (ll i = 2; i * i <= n; i++) {
if (n % i == 0) {
ans = ans / i * (i - 1ll);
while (n % i == 0) n /= i;
}
}
if (n > 1ll) ans = ans / n * (n - 1ll);
return ans;
}
如果是多个数的欧拉函数值,可以利用后面会提到的线性筛法来求得。 详见:筛法求欧拉函数
欧拉定理
与欧拉函数紧密相关的一个定理就是欧拉定理。其描述如下:
$ 若gcd(a, m) = 1,则 a^{\phi(m)} \equiv 1 (mod m)$
扩展欧拉定理
筛法
线性筛法
constexpr int maxn = 1e6;
int pnl[maxn + 10], cnt;//pnl
bool st[maxn + 10]; //索引为质数值就是0
void init_primes() {
st[0] = 1;
st[1] = 1;
for (int i = 2; i <= maxn; ++ i) {
if (st[i] == 0) pnl[cnt++] = i;
for (int j = 0; pnl[j] <= maxn / i; ++j) {
st[pnl[j] * i] = 1;
if (i % pnl[j] == 0) break;
}
}
}
注意到这个筛法还能求每个数的最小质因子
int minp[maxn]; //值为该索引的最小的质因子
void init_primes() {
iota(minp, minp + maxn, 0);
for (int i=2; i <= maxn; ++i) {
if (minp[i] == i) { pnl[cnt++] = i; }
for (int j=0; pnl[j] <= maxn / i; ++j) {
minp[pnl[j] * i] = min(minp[pnl[j] * i], pnl[j]);
if (i % pnl[j] == 0) { break; }
}
}
}
筛法求欧拉函数
constexpr int N = 1e5;
vector<int> pri;
bool not_prime[N];
int phi[N];
void pre(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!not_prime[i]) {
pri.push_back(i);
phi[i] = i - 1;
}
for (int pri_j : pri) {
if (i * pri_j > n) break;
not_prime[i * pri_j] = true;
if (i % pri_j == 0) {
phi[i * pri_j] = phi[i] * pri_j;
break;
}
phi[i * pri_j] = phi[i] * phi[pri_j];
}
}
}
裴蜀定理
\( 对于二元方程ax + by = c \)
\( 当且仅当c = gcd(a , b)时 \)
\( x , y 存在整数解 \)
\( 当c \neq gcd(a , b) \)
\( x , y 不存在整数解,但有非整数解 \)
推广:
\(
一定存在整数x , y , 满足ax + by = gcd(a , b) * n
\)
再推广:
\(
一定存在整数\{ x_{i} \vert i \in [1 , n] \},满足
\)
莫比乌斯反演
莫比乌斯函数
定义
\( \mu (n) = \begin{cases} 1 ~~~~~~~~~~~ n = 1 \\ (-1)^{s} ~~~ s为n的质因子个数 \\ 0 ~~~~~~~~~~~ n含有相同质因子 \\ \end{cases} \)
性质
\( \sum\limits_{d \vert n} \mu (d) = [n = 1] \)
\( \sum\limits_{d \vert gcd(i, j)} \mu (d) = [gcd(i, j) = 1] \)
\( \sum\limits_{d \vert n} \mu (d) \frac{n}{d} = \phi(n) \)
生成莫比乌斯函数的代码
constexpr ll N = 1e6;
int mu[N + 1];
int p[N + 1];
bool vis[N + 1];
int cnt;
void initmu () {
mu[1] = 1;
for (int i = 2; i <= N; ++ i) {
if (!vis[i]) {
p[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * p[j] <= N; ++ j) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i % p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}
例题
\( 已知n, m求\sum\limits_{i = 1}^{n} \sum\limits_{j = 1}^{m} lcm(i, j) (mod) \)
\( = \sum\limits_{d = 1}^{n} d \sum\limits_{k = 1}^{\lfloor \frac{n}{d} \rfloor} \mu(k) k^{2} \sum\limits_{i = 1}^{\lfloor \frac{n}{dk} \rfloor} i \sum\limits_{j = 1}^{\lfloor \frac{m}{dk} \rfloor} j \)
设
\( F(n, m) = \sum\limits_{k = 1}^{n} \mu(k) k^{2} \sum\limits_{i = 1}^{\lfloor \frac{n}{k} \rfloor} i \sum\limits_{j = 1}^{\lfloor \frac{m}{k} \rfloor} j \)
\( G(n, m) = \sum\limits_{i = 1}^{n} i \sum\limits_{j = 1}^{m} j \)
constexpr ll N = 1e7 + 10;
int mu[N + 1];
int p[N + 1];
ll s[N + 1];
bool vis[N + 1];
int cnt;
void initmu () {
mu[1] = 1;
for (int i = 2; i <= N; ++ i) {
if (!vis[i]) {
p[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * p[j] <= N; ++ j) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i % p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
for (int i = 1; i <= N; ++ i) {
s[i] = (s[i - 1] + (ll)mu[i] * i * i % mod + mod) % mod;
}
}
ll G(ll n, ll m) {
return (n * (n + 1ll) / 2ll % mod) * (m * (m + 1ll) / 2ll % mod) % mod;
}
ll F(ll n, ll m) {
ll res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
res = (res + (ll)(s[r] - s[l - 1]) * G(n / l, m / l) % mod + mod) % mod;
}
return res;
}
int calc (ll n, ll m) {
ll res = 0;
if (n > m) swap(n, m);
for (ll l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
res = (res + (ll)(r - l + 1) * (l + r) / 2ll % mod * F(n / l, m / l) % mod) % mod;
}
return res;
}
多项式与生成函数
普通生成函数
广义二项式定理
指数生成函数
有序数列的生成函数
狄利克雷生成函数(DGF)
定义
(即分子下标相乘等于分母的底数)
f(n), g(n)是两个积性函数,
\( (f * g)(n) = \sum\limits_{d \vert} \)
三个常用函数
\( 1.元函数 \epsilon(n) = [n = 1] \)
\( 2.常数函数 1(n) = 1 \)
\( 3.恒等函数 id(n) = n \)
常用卷积关系
\( 1.\sum\limits_{d \vert n} \mu(d) = [n = 1] ~~~ \Leftrightarrow ~~~ \mu * 1 = \epsilon \)
\( 2.\sum\limits_{d \vert n} \phi(d) = n ~~~~~~~~~~~~~ \Leftrightarrow ~~~ \phi * 1 = id \)
\( 3.\sum\limits_{d \vert n} \mu(d) \frac{n}{d} ~~~~~~~~~~~~~~~~~~ \Leftrightarrow ~~~ \mu * id = \phi \)
\( 4.f * \epsilon = f \)
\( 5.f * 1 \neq f \)
组合数学
排列组合
排列组合基础
下面介绍求组合数的方法
1.杨辉三角法
\( 根据 C_{n}^{m} = C_{n - 1}^{m} + C_{n - 1}^{m - 1} ~~~ 可O(n^{2})预处理C_{n}^{m}的值 \)
constexpr ll CN = 10000;
ll C[CN + 10][CN + 10];
// ll A[CN + 7][CN + 7];
// ll fact[CN + 7];
ll H[CN + 7];
void initc() { // combinatorics
// fact[1] = 1ll;
for (ll i = 0; i <= CN; ++i) {
// if (i > 1) fact[i] = fact[i - 1] % mod * i % mod;
for (int j = 0; j <= i; ++j) {
if (!j) {
C[i][j] = 1ll;
} else {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
// A[i][j] = C[i][j] % mod * fact[j] % mod;
}
}
}
2.公式法
\( 根据 C_{n}^{m} = \frac{n!}{m!(n-m)!} ~~~ 可O(n)预处理阶乘,再通过公式计算 \)
constexpr ll maxn = 10000;
ll fact[maxn + 2];
ll inv[maxn + 2];
void initfact() {
fact[0] = 1;
for (ll i = 1; i <= maxn; ++ i) {
fact[i] = fact[i - 1] * i % mod;
}
inv[maxn] = power(fact[maxn], mod - 2);
for (ll i = maxn - 1; i >= 1; -- i) {
inv[i] = inv[i + 1] * (i + 1ll) % mod;
}
}
ll C(int n, int m) {
if (m > n) {
return 0;
} else if (m == 0 || n == m) {
return 1;
} else {
if (n > maxn) {
ll res = 1, inv = 1;
for (ll k = 1; k <= m; ++ k) {
res = res * (n - k + 1ll) % mod;
inv = inv * k % mod;
}
res = res * power(inv, mod - 2) % mod;
return res;
} else {
return fact[n] * inv[m] % mod * inv[n - m] % mod;
}
}
}
\( C_{n}^{m} = C_{n - 1}^{m - 1} + C_{n - 1}^{m} \)
\(\sum\limits_{k = m}^{n} C_{k}^{m} = C_{n + 1}^{m + 1}\)
多重集
二项式定理
\( \tbinom{n}{m} = C_{n}^{m} = \frac{n!}{m!(n-m)!} \)
n个空位,分配m个人进去(可重复)
广义二项式定理
容斥原理
例题:求分母不超过n的所有最简真分数的个数与他们的和 (n <= 1e6)
显然有:
\( ans = \sum\limits_{a = 1}^{n} \sum\limits_{b = 1}^{a} ([b < a] * [gcd(a , b) == 1]) = (\sum\limits_{a = 1}^{n} \sum\limits_{b = 1}^{n} [gcd(a , b) == 1]) / 2 \)
那么只需求出n以内的互质对即可。即以1为最大公约数的数对
由容斥原理可以得知,先找到所有以1为公约数的数对,再从中剔除所有以1的倍数为公约数的数对,余下的数对就是以1为最大公约数的数对。即f(k)=以1为公约数的数对个数 - 以1的倍数为 公约数 的数对个数。
可以注意到,当(x,y)为互质时,(y-x,y)也互质。那么有这些最简真分数的和就是它们的个数除以2。
代码实现如下:
ll nn = 2e6;
cin >> nn;
vector<ll> f(nn + 1);// f[i] 表示 gcd == i 的情况有几种
for (ll k = nn; k >= 1; k--) {
f[k] = (nn / k) * (nn / k);//以k为公约数的数对
for (ll i = k + k; i <= nn; i += k) { //减去以k的倍数为公约数的数对
f[k] -= f[i];
}
}
ans1 = f[1] / 2;
ans2 = (double)ans1 / 2.0;
cout << ans1 << " " << ans2 << "\n";
卡特兰数
通项公式:
递推公式:
Catalan 特征:
从(0,0)到(n,n),不越过对角线,即任何时候,向上走的步数不能超过向右走的步数。
一种操作数不能超过另一种操作数,或者两种操作数不能有交集,这些操作的方案数通常是卡特兰数
Catalan 应用:
1.一个有n个0和n个1组成的字串,且所有的前缀字串满足1的个数不超过0的个数。这样的字串个数是多少?
2.包含n组括号的合法运算式的个数有多少?
3.一个栈的进栈序列为1,2,3,~,n,有多少个不同的出栈序列?
4.n个结点可构造多少个不同的二叉树?
5.在圆上选择2n个点,将这些点成对连接起来使得所得到的n条弦不相交的方法数?
6.通过连结顶点而将n+2边的凸多边形分成n个三角形的方法数?
线性代数
行列式
int cal(int **det, int n) { // det-行列式,n:行列式的阶数
int detVal = 0; // 行列式的值
if (n == 1) { return det[0][0]; } // 递归终止条件
int **tempdet = new int *[n - 1]; // 用来存储余相应的余子式
for (int i = 0; i < n - 1; i++) { tempdet[i] = new int[n - 1]; }
for (int i = 0; i < n; i++) { // 第一重循环,行列式按第一行展开
for (int j = 0; j < n - 1; j++) {
for (int k = 0; k < n - 1; k++) {
if (k < i) { tempdet[j][k] = det[j + 1][k]; }
else { tempdet[j][k] = det[j + 1][k + 1]; }
}
}
detVal += det[0][i] * power(-1, i) * cal(tempdet, n - 1);
}
return detVal;
}
int main() {
int n , m;
cin >> n >> m;
int order; // 输入行列式的阶数
int **det = new int *[order]; // 需要动态内存
for (int i = 0; i < order; i++) {
det[i] = new int[order];
}
for (int i = 0; i < order; ++ i) {
for (int j = 0; j < order; ++ j) {
cin >> det[i][j];
}
}
ll anscal = cal(det, order);
}
博弈论
有向图博弈
int SG[N];
int k;
int sg(int x) {
if (x < 0) return -1;
if (SG[x]) return SG[x];
set<int> s;
s.insert(sg(x - 1));
s.insert(sg(x - 2));
s.insert(sg(x - k));
for (int i = 0; ; i++) {
if (!s.count(i)) {
return SG[x] = i;
}
}
}
https://www.acwing.com/blog/content/13279/
数据结构(DS)
单调栈
class MS { // Monotone stack
public:
int n, top;
vector<ll> sta;
MS() {}
MS(int n) { init(n); };
void init(int n) {
this -> n = n;
top = 0;
sta.resize(n + 1, inf);
sta[0] = 0;
}
void insert(ll x) {
while (x <= sta[top]) {
top --;
}
sta[++top] = x;
}
void show() {
for (int i = 1; i <= top; ++ i) {
cout << sta[i] << " \n"[i == top];
}
}
};
并查集
class DSU {
public:
int n;
vector<int> dsu, dsus;
// dsu[i]存是 i 的祖宗结点
// dsus[i]存 i 所在图的大小
DSU() {}
DSU(int n) { init(n); }
void init(int n) {
this -> n = n;
dsu.resize(n + 1);
dsus.resize(n + 1, 1);
iota(dsu.begin(), dsu.end(), 0);
}
int find(int a) {
if (a == dsu[a]) {
return a;
} else {
dsu[a] = find(dsu[a]);
return dsu[a];
}
}
void merge(int a, int b) {
int x = find(a);
int y = find(b);
if (x != y) {
if (dsus[x] < dsus[y]) { swap(x, y); }
dsu[y] = x; dsus[x] += dsus[y];
}
}
// 由于未必路径压缩好了,所以求祖宗结点需调用find函数或者使用reboot()函数路径压缩
void reboot() {
for (int i = 1; i <= n; ++ i) {
int fa = find(dsu[i]);
dsu[i] = fa; dsus[i] = dsus[fa];
}
}
};
树状数组
树状数组是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。
普通树状数组维护的信息及运算要满足 结合律 且 可差分 如加法(和)、乘法(积)、异或等。
\( \bullet 结合律:(x \circ y) \circ z = x \circ (y \circ z),其中 \circ 是一个二元运算符 \)
\( \bullet 可差分:具有逆运算的运算,即已知 x \circ y 和 x 可以求出 y。 \)
class BIT {
public:
int n;
vector<ll> t;
BIT() {}
BIT(int n) { init(n); }
void init(int n) {
this -> n = n;
t.resize(n + 1);
}
int lowbit(int x) {return x & (- x);};
// O(n)建树
void build(int n, vector<ll> &s) {
for (int i = 1; i <= n; ++ i) {
t[i] += s[i];
int fa = i + lowbit(i);
if (fa <= n) {
t[fa] += t[i];
}
}
}
void add (int x , int y) {
for (int pos = x; pos <= n; pos += lowbit(pos)) {
t[pos] += y;
}
}
ll query (int x) {
ll res = 0;
for(int pos = x; pos; pos -= lowbit(pos)) {
res += t[pos];
}
return res;
}
ll query (ll l, ll r) {
return query(r) - query(l - 1);
}
};
点修区查
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, m;
cin >> n >> m;
vector<ll> a(n + 1);
for (int i = 1; i <= n; ++ i) cin >> a[i];
BIT bit(n);
bit.build(n, a);
for (int i = 1; i <= m; ++ i) {
int op;
cin >> op;
if (op == 1) {
ll x, k;
cin >> x >> k;
bit.add(x, k);
} else {
ll x , y;
cin >> x >> y;
ll ans = bit.query(x, y);
cout << ans << "\n";
}
}
}
区修点查
auto dif = [&](vector<ll> &a, int lena) {
vector<ll> da(lena + 1);
for (int i = 1; i <= lena; ++ i) {
da[i] = a[i] - a[i - 1];
}
return da;
};
int n, m;
cin >> n >> m;
vector<ll> a(n + 1);
for (int i = 1; i <= n; ++ i) cin >> a[i];
vector<ll> da = dif(a, n);
BIT bit(n);
bit.build(n, da);
for (int i = 1; i <= m; ++ i) {
int op;
cin >> op;
if (op == 1) {
ll x, y, k;
cin >> x >> y >> k;
bit.add(x, k);
bit.add(y + 1, -k);
} else {
ll x;
cin >> x;
ll ans = bit.query(x);
cout << ans << "\n";
}
}
线段树
class info {
public:
int l, r;
int sum = 0;
info () {}
info (int l, int r, int c) :
sum(c),
l(l), r(r) {};
void merge(info l, info r) {
sum = l.sum + r.sum;
};
};
class ST {
public:
int n;
vector<info> tr;
int ls(int x) { return x << 1; };
int rs(int x) { return x << 1 | 1; };
ST() {}
ST(int n, vector<info> s) {
this->n = n;
tr.resize(4 * n + 16);
build(n, s);
}
void build(int l, int r, int p, vector<info> &s) {
tr[p] = s[l];
tr[p].r = r;
if (l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, ls(p), s);
build(mid + 1, r, rs(p), s);
tr[p].merge(tr[ls(p)], tr[rs(p)]);
}
void build(int n, vector<info> &s) { build(1, n, 1, s); };
void change0(int u, int x, int w) {
if (tr[u].l == tr[u].r) {
tr[u] = info(x, x, w);
return ;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) {
change0(ls(u), x, w);
} else {
change0(rs(u), x, w);
}
tr[u].merge(tr[ls(u)], tr[rs(u)]);
}
void change(int x , int w) { change0(1, x, w); };
info query0(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u];
int mid = (tr[u].l + tr[u].r) >> 1;
if (r <= mid) return query0(ls(u), l, r);
if (l > mid) return query0(rs(u), l, r);
info T;
T.merge(query0(ls(u), l, mid), query0(rs(u), mid + 1, r));
return T;
}
info query(int l, int r) { return query0(1, l, r); };
void show() {
set<int> st;
cout << "ST:\n";
for (int i = 1; (int)st.size() < n; ++ i) {
if (tr[i].l == tr[i].r) st.insert(tr[i].l);
cout << tr[i].l << " ";
cout << tr[i].r << " ";
cout << tr[i].sum << " ";
cout << "\n";
}
}
};
int main() {
int n, q;
vector<info> a(n + 1);
ST t(n, a);
}
图论(graph)
树上问题
最近公共祖先
int n, root, q, logn = 32;
vector<int> dep(n + 10); // 点的深度
vector<vector<int>> adj(n + 10, vector<int>(0)); // 图
vector<vector<int>> fa(n + 10, vector<int>(logn + 1)); // fa[u][i]表示从u点向上跳2^i层的祖先结点
dep[root] = 0;
//不同于树形DP,根节点还需要有一个超级原点,即0,故需要处理根节点和0
auto dfs = [&](auto self , int x , int father) -> void {
fa[x][0] = father;
dep[x] = dep[father] + 1;
for (int i = 1; i <= logn; ++ i) {
fa[x][i] = fa[ fa[x][i - 1] ][i - 1];
}
for (auto y : adj[x]) {
if (y == father) continue;
self(self , y , x);
}
};
dfs(dfs, root, 0);
auto lca = [&](int x, int y) {
if (dep[x] > dep[y]) swap(x, y);
for (int i = logn; i >= 0; -- i) {
if (dep[fa[y][i]] >= dep[x]) y = fa[y][i];
}
if (y == x) return x;
for (int i = logn; i >= 0; -- i) {
if (fa[y][i] != fa[x][i]) {
y = fa[y][i];
x = fa[x][i];
}
}
return fa[y][0];
};
树链剖分
重链剖分
class HLD { // Heavy-light Decomposition
public:
int n;
vector<int> top, fa, dep, siz, son, dfn, rnk;
vector<vector<int>> adj;
int cnt;
HLD() {}
HLD(int n) { init(n); }
void init(int n) {
this -> n = n;
top.resize(n + 1); // top[u] 存 u 所在重链的顶点
fa.resize(n + 1); // fa[u] 存 u 的父结点
dep.resize(n + 1); // dep[u] 存 u 的深度
siz.resize(n + 1); // siz[u] 存 u 的子树结点数
son.resize(n + 1); // son[u] 存 u 的重儿子
dfn.resize(n + 1); // dfn[u] 存 u 的 DFS 序,也是其在线段树中的编号
rnk.resize(n + 1); // rnk[u] 存 u 的 DFS 序对应的结点编号,有rnk[dfn[u]] = u
adj.resize(n + 1, {}); // 树
cnt = 0;
}
void add(int u, int v) {
adj[u].emplace_back(v);
adj[v].emplace_back(u);
}
void build(int root = 1) {
top[root] = root;
dfs1(root, 0);
dfs2(root, root);
}
void dfs1(int u, int father) {
fa[u] = father;
dep[u] = dep[father] + 1;
siz[u] = 1;
for (auto &v : adj[u]) {
if (v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt;
rnk[cnt] = u;
if (!son[u]) return ;
dfs2(son[u], t);
for (auto &v : adj[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int lca(int u, int v) { // 最近公共祖先
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) {
u = fa[top[u]];
} else {
v = fa[top[v]];
}
}
return dep[u] < dep[v] ? u : v;
}
int dist(int u, int v) { // 树上两点最短距离
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
};
拓扑排序
vector<vector<int>> adj(n + 1, vector<int>());
vector<int> tp; // 拓扑序数组
vector<int> din(n + 1); // 入度,即指向该点的箭头数
auto toposort = [&]() {
queue<int> q;
for (int i = 1; i <= n; ++ i) {
if (din[i] == 0) q.push(i);
}
while (q.size()) {
int x = q.front();
q.pop();
tp.push_back(x);
for (auto y : adj[x]) {
din[y]--;
if (din[y] == 0) q.push(y);
}
}
return tp.size() == n;
};
最短路
Dijkstra 算法
求解非负权图上单源最短路径的算法。
class edge {
public:
ll v, w;
};
struct node {
ll dis, u;
bool operator>(const node& a) const { return dis > a.dis; }
};
复杂度O(mlog(m))版本:
vector<vector<edge>> g(n + 1, vector<edge>(0));
vector<ll> pre(n + 1);
vector<int> path(n + 1);
auto dij = [&](int begin, int end) {
vector<ll> dis(n + 1 , inf);
vector<ll> vis(n + 1);
priority_queue<node, vector<node>, greater<node>> q;
dis[begin] = 0;
q.push({0, begin});
while (!q.empty()) {
int u = q.top().u;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto ed : g[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
pre[v] = u;
q.push({dis[v], v});
}
}
}
return dis[end];
};
auto dfs_path = [&](auto &self, int s, int t) -> void {
path[t] = 1;
if (t == s) {
return ;
} else {
self(self, s, pre[t]);
debug(t);
}
};
复杂度O(n^2)版本:
vector<vector<edge>> g(n + 1, vector<edge>(0));
auto dij = [&](int begin, int end) {
vector<ll> dis(n + 1 , inf);
vector<ll> vis(n + 1);
dis[begin] = 0;
for (int i = 1; i <= n; ++ i) {
ll u = 0, mind = inf;
for (int j = 1; j <= n; ++ j) {
if (!vis[j] && dis[j] < mind) {
u = j;
mind = dis[j];
}
}
vis[u] = 1;
for (auto ed : g[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) dis[v] = dis[u] + w;
}
}
return dis[end];
};
最小生成树(MST)
edge
class edge {
public:
ll u, v, w;
edge() {}
edge(ll u, ll v, ll w) : u(u), v(v), w(w) {}
bool operator<(const edge& a) const { return w < a.w; }
};
Kruskal算法
class DSU;
int main() {
int n, m;
cin >> n >> m;
vector<vector<edge>> adj(n + 1, vector<edge>(0));
vector<edge> g(m + 1);
DSU a(n);
for (int i = 1; i <= m; ++ i) {
ll u, v, w;
cin >> u >> v >> w;
g[i] = edge(u, v, w);
}
auto Kruskal = [&]() {
sort(g.begin() + 1, g.end());
ll cnt = 0, res = 0;
for (int i = 1; i <= m; ++ i) {
int xr = a.find(g[i].u), yr = a.find(g[i].v);
if (xr != yr) {
a.merge(xr, yr);
cnt ++;
res += g[i].w;
}
}
if (cnt == n) {
return res;
} else {
return -1ll;
}
};
Kruskal();
}
Prim算法
struct node {
ll u, dis;
node() {}
node(ll u, ll dis) : u(u), dis(dis) {}
bool operator<(const node& a) const { return dis > a.dis; }
};
int main() {
int n, m;
vector<vector<edge>> adj(n + 1, vector<edge>(0));
vector<ll> dis(n + 1);
vector<int> vis(n + 1, 0);
priority_queue<node> q;
auto addEdge = [&](ll u, ll v, ll w) {
adj[u].emplace_back(edge(u, v, w));
adj[v].emplace_back(edge(v, u, w));
};
auto Prim = [&](int root = 1) {
ll res = 0, cnt = 0;
for (int i = 1; i <= n; ++ i) dis[i] = inf;
dis[root] = 0;
q.push(node(root, 0));
while (!q.empty()) {
if (cnt >= n) break;
ll u = q.top().u, d = q.top().dis;
q.pop();
if (vis[u]) continue;
vis[u] = true;
cnt ++;
res += d;
for (auto ed : adj[u]) {
ll v = ed.v, w = ed.w;
if (w < dis[v]) {
dis[v] = w;
q.push({v, w});
}
}
}
if (cnt == n) {
return res;
} else {
return -1ll;
}
};
Prim(root);
}
计算几何
二维
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using DB = double;
const DB eps = 1e-9, pi = acos(-1), inf = 1e100;
int dcmp(DB x, DB y) { return fabs(x - y) < eps ? 0 : x > y ? 1 : -1; } // 比较大小
int sgn(DB d) { return fabs(d) < eps ? 0 : d > 0 ? 1 : -1; } // 判断正负
struct Point { // 定义点
DB x = 0, y = 0;
Point () {}
Point (DB x, DB y) : x(x), y(y) {}
Point operator+(const Point &P) const { return Point(x + P.x, y + P.y); }
Point operator-(const Point &P) const { return Point(x - P.x, y - P.y); }
Point operator*(DB p) const { return Point(x * p, y * p); }
Point operator/(DB p) const { return Point(x / p, y / p); }
DB operator&(const Point &P) const { return x * P.x + y * P.y; } // 点积 |A||B|cosθ
DB operator^(const Point &P) const { // 叉积 |A||B|sinθ
return x * P.y - y * P.x;
}
bool operator<(const Point &P) const {
return sgn(x - P.x) < 0 || (sgn(x - P.x) == 0 && sgn(y - P.y) < 0);
}
bool operator==(const Point &P) const { return !sgn(x - P.x) && !sgn(y - P.y); }
bool operator!=(const Point &P) const { return sgn(x - P.x) || sgn(y - P.y); }
friend istream &operator>>(istream &is, Point &rhs) {
return is >> rhs.x >> rhs.y;
}
friend ostream &operator<<(ostream &os, const Point &rhs) {
return os << '(' << rhs.x << ',' << rhs.y << ')';
}
};
using Vector = Point;
const Point O = {0, 0}; // 原点
// 向量A长度
DB Len(const Vector &A) { return sqrt(A & A); }
// 向量A长度的平方
DB Len2(const Vector &A) { return A & A; }
// 向量A,B夹角
DB Angle(const Vector &A, const Vector &B) { return acos((A & B) / Len(A) / Len(B)); }
// 两点间距离
DB Distance(const Point &A, const Point &B) { return Len(A - B); }
// ab在ac上的投影长度
DB Project(const Point &A, const Point &B, const Point &C) {
return (B - A & C - A) / Len(B - A);
}
// 向量A,B构成的平行四边形的有向面积
DB Area2(const Point &A, const Point &B, const Point &C) { return B - A ^ C - A; }
// 斜率
DB Slope(const Point &A, const Point &B) { return (A.y - B.y) / (A.x - B.x); }
// 向量A逆时针转动rad(弧度)
Vector Rotate(const Vector &A, DB rad) {
return Vector(A.x * cos(rad) - A.y * sin(rad), A.x * sin(rad) + A.y * cos(rad));
}
// 返回A的单位向量
Vector Norm(const Vector &P) { return P / Len(P); }
// 求凸包
vector<Point> Convex_hull(vector<Point> &p) {
sort(p.begin(), p.end());
p.erase(unique(p.begin(), p.end()), p.end());
int n = size(p);
vector<Point> ch;
// set<Point> st;
for (int i = 0; i < n; ++ i) {
while (size(ch) > 1 && sgn(Area2(ch.rbegin()[1], ch.rbegin()[0], p[i])) <= 0) {
// st.erase(prev(st.end()));
ch.pop_back();
}
ch.push_back(p[i]);
// st.insert(p[i]);
}
// st.erase(st.begin());
for (int i = n - 1, j = size(ch); i >= 0; -- i) {
// if (st.count(p[i])) continue;
while (size(ch) > 1 && sgn(Area2(ch.rbegin()[1], ch.rbegin()[0], p[i])) <= 0) {
ch.pop_back();
}
ch.push_back(p[i]);
}
if (size(ch) > 1) ch.pop_back();
return ch;
}
// 平面最近点对
DB Closest_pair(const vector<Point> &p, int l, int r) {
DB dist = inf;
if (l == r) return dist;
if (l + 1 == r) return Distance(p[l], p[r]);
int mid = l + r >> 1;
DB d1 = Closest_pair(p, l, mid), d2 = Closest_pair(p, mid + 1, r);
dist = min(d1, d2);
vector<Point> tmp;
for (int i = l; i <= r; ++ i)
if (fabs(p[mid].x - p[i].x) <= dist) tmp.push_back(p[i]);
for (int i = 0; i < tmp.size(); ++ i)
for (int j = i + 1; j < tmp.size(); ++ j) {
if (tmp[j].y - tmp[i].y >= dist) break;
dist = min(dist, Distance(tmp[i], tmp[j]));
}
return dist;
}
// 旋转卡壳,凸包直径
DB Rotating_calipers_point_pair_min(const vector<Point> &P) {
if (size(P) <= 2) return Distance(P[0], P[1]);
DB ans = 0;
for (int i = 0, j = 2, n = size(P); i < n; ++ i) {
auto a = P[i], b = P[(i + 1) % n];
while (dcmp(Area2(a, b, P[j]), Area2(a, b, P[(j + 1) % n])) < 0) j = (j + 1) % n;
ans = max({ans, Distance(a, P[j]), Distance(b, P[j])});
}
return ans;
}
// 旋转卡壳,返回最小矩形面积和矩形四点
DB Rotaring_calipers_area_min(const vector<Point> &p, array<Point, 4> &ans) {
int n = size(p);
DB min_area = inf;
for (int i = 0, a = 2, b = 1, c = 2; i < n; ++ i) {
auto d = p[i], e = p[(i + 1) % n];
while (dcmp(Area2(d, e, p[a]), Area2(d, e, p[(a + 1) % n])) < 0) a = (a + 1) % n;
while (dcmp(Project(d, e, p[b]), Project(d, e, p[(b + 1) % n])) < 0) b = (b + 1) % n;
if (i == 0) c = a;
while (dcmp(Project(d, e, p[c]), Project(d, e, p[(c + 1) % n])) > 0) c = (c + 1) % n;
auto x = p[a], y = p[b], z = p[c];
auto h = Area2(d, e, x) / Len(e - d);
auto w = ((y - z) & (e - d)) / Len(e - d);
if (h * w < min_area) {
min_area = h * w;
ans[0] = d + Norm(e - d) * Project(d, e, y);
ans[3] = d + Norm(e - d) * Project(d, e, z);
auto u = Norm(Rotate(e - d, pi / 2));
ans[1] = ans[0] + u * h;
ans[2] = ans[3] + u * h;
}
}
return min_area;
}
struct Line { // 定义直线
Point p1, p2;
Line() {}
Line(const Point &p1, const Point &p2) : p1(p1), p2(p2) {}
Line (const Point &p, DB angle) { // 根据点和倾斜角angle确定直线,0 <= angle <= pi
p1 = p;
if (sgn(angle - pi / 2) == 0) p2 = p1 + Point(0, 1);
else p2 = p1 + Point(1, tan(angle));
}
Line(DB a, DB b, DB c) { // ax + by + c = 0
if (sgn(a) == 0) p1 = Point(0, -c / b), p2 = Point(1, -c / b);
else if (sgn(b) == 0) p1 = Point(-c / a, 0), p2 = Point(-c / a, 1);
else p1 = Point(0, -c / b), p2 = Point(1, (-c - a) / b);
}
};
using Segment = Line;
const Line Ox = {O, {1, 0}}, Oy = {O, {1, 0}};
// 点和线的位置关系
int Point_line_relation(const Point &p, const Line &v) {
int c = sgn(p - v.p1 ^ v.p2 - v.p1);
return c == 0 ? 0 : c < 0 ? 1 : 2; // 0:p在v上,1:p在v左侧,2:p在v右侧
}
// 点到直线的距离
DB Dis_point_line(const Point &p, const Line &v) {
return fabs(p - v.p1 ^ v.p2 - v.p1) / Distance(v.p1, v.p2);
}
// 点到线段的距离
DB Dis_point_seg(const Point &p, const Segment &v) {
if (sgn(p - v.p1 & v.p2 - v.p1) < 0 || sgn(p - v.p2 & v.p1 - v.p2) < 0) {
return min(Distance(p, v.p1), Distance(p, v.p2));
}
// 点的投影在线段上
return Dis_point_line(p, v);
}
// 返回线段上的某一点
Point Point_on_seg(const Point &A, const Point &B, DB d) {
return A + (B - A) * d / Distance(A, B);
}
// 判断点是否在线段上
bool Point_on_seg(const Point &p, const Segment &v) {
return !sgn(p - v.p1 ^ v.p2 - v.p1) && sgn(p - v.p1 & p - v.p2) <= 0;
}
// 判断线段ab和cd是否相交(包括端点,去掉等于即不包括端点)
bool Corss_segment(const Point &a, const Point &b, const Point &c, const Point &d) {
DB c1 = b - a ^ c - a, c2 = b - a ^ d - a;
DB d1 = d - c ^ a - c, d2 = d - c ^ b - c;
return sgn(c1) * sgn(c2) <= 0 && sgn(d1) * sgn(d2) <= 0; // 1相交,0,不相交
}
int Line_relation(const Line &v1, const Line &v2) { // 两条直线的位置关系
if (sgn(v1.p2 - v1.p1 ^ v2.p2 - v2.p1) == 0) {
if (Point_line_relation(v1.p1, v2) == 0) return 1; // 重合
return 0; // 平行
}
return 2; // 相交
}
// 直线与线段是否相交
bool Line_Segment_relation(const Line &v1, const Segment &v2) {
// return sgn(Cross(v1.p2 - v1.p1, v2.p1 - v1.p1)
* Cross(v1.p2 - v1.p1, v2.p2 - v1.p1)) <= 0;
return sgn(Area2(v1.p1, v1.p2, v2.p1) * Area2(v1.p1, v1.p2, v2.p2)) <= 0;
}
// 两条直线的交点,线段1:AB,线段2:CD
// 使用前保证直线AB,CD不共线且不平行
Point Cross_point(const Point &A, const Point &B, const Point &C, const Point &D) {
DB s1 = B - A ^ C - A, s2 = B - A ^ D - A;
return Point(C.x * s2 - D.x * s1, C.y * s2 - D.y * s1) / (s2 - s1);
}
// 点在直线上的投影
Point Point_line_proj(const Point &p, const Line &v) {
DB k = (v.p2 - v.p1 & p - v.p1) / Len2(v.p2 - v.p1);
return v.p1 + (v.p2 - v.p1) * k;
}
// 点关于直线的对称点
Point Point_line_symmetry(const Point &p, const Line &v) {
Point q = Point_line_proj(p, v);
return Point(2 * q.x - p.x, 2 * q.y - p.y);
}
// 求多边形面积
DB Polygon_area(const vector<Point> &p) {
DB area = 0;
for (int i = 0, n = size(p); i < n; ++ i)
area += p[i] ^ p[(i + 1) % n];
return area / 2;
}
// 判断点跟多边形的位置关系
int Point_in_polygon(Point &p, vector<Point> &poly) {
for (auto &x : poly)
if (x == p) return 3; // 点在多边形的顶点上
int n = poly.size();
for (int i = 0; i < n; ++ i) // 点在多边形的边上
if (Point_on_seg(p, Line(poly[i], poly[(i + 1) % n]))) return 2;
int num = 0;
for (int i = 0; i < n; ++ i) {
int c = sgn((p - poly[i]) ^ (p - poly[(i + 1) % n]));
int u = sgn(poly[i].y - p.y), v = sgn(poly[(i + 1) % n].y - p.y);
if (c > 0 && u < 0 && v >= 0) num ++ ;
if (c < 0 && u >= 0 && v < 0) num -- ;
}
return num != 0; // 1:点在外部,0:点在内部
}
Point Polygon_center(const vector<Point> &p) { // 返回多边形的重心
int n = p.size();
Point ans = Point(0, 0);
if (Polygon_area(p) == 0) return ans ;
for (int i = 0; i < n; ++ i)
ans = ans + (p[i] + p[(i + 1) % n]) * (p[i] ^ p[(i + 1) % n]);
return ans / Polygon_area(p) / 6;
}
// 多边形上的格点个数
int Grid_onedge(const vector<Point> &p) {
int cnt = 0;
for (int i = 0, n = size(p); i < n; ++ i)
cnt += gcd((int)abs(p[i].x - p[(i + 1) % n].x), (int)abs(p[i].x - p[(i + 1) % n].y));
return cnt;
}
int Gird_inside(const vector<Point> &p) {
int cnt = 0;
for (int i = 0, n = size(p); i < n; ++ i)
cnt += p[(i + 1) % n].y * (p[i].x - p[(i + 2) % n].x);
return (abs(cnt) - Grid_onedge(p)) / 2 + 1;
}
/*
struct Line { // 有向直线,它的左边就是对应的半平面
Point p; // 直线上任意一点
Vector v; // 方向向量,左边就是对应的半平面
DB deg; // 极角,从x正半轴旋转到v的角度
Line (const Point &p, const Vector &v) : p(p), v(v) { deg = atan2(v.y, v.x); }
bool operator<(const Line &L) { return deg < L.deg; } // 极角排序
};
using Segment = Line;
// 点是否在直线上(y = kx + b)
bool Point_line_relation(const Point &p, const Line &L) {
DB y = L.p.y + (p.x - L.p.x) * L.v.y / L.v.x;
return dcmp(y, p.y) == 0;
}
// 点p在线L左边,即点p在线L外面
bool OnLeft(const Line &L, const Point &p) { return sgn(L.v ^ (p - L.p)) < 0; }
Point Cross_point(const Line &a, const Line &b) { // 两直线交点(点线式)
double t = (b.v ^ (a.p - b.p)) / (a.v ^ b.v);
return a.p + a.v * t;
}
vector<Point> half_plane_intersection(vector<Line> &L) {// 求半平面交,返回凸多边形
sort(L.begin(), L.end()); // 极角排序
int n = size(L), hh = 0, tt = 0;
vector<Point> p(n);
vector<Line> q(n);
q[0] = L[0];
for (int i = 1; i < n; ++ i) {
while (hh < tt && !OnLeft(L[i], p[tt - 1])) tt -- ; // 删除尾部半平面
while (hh < tt && !OnLeft(L[i], p[hh])) hh ++ ; // 删除首部半平面
q[ ++ tt] = L[i];
if (sgn(q[tt].v ^ q[tt - 1].v) == 0) { // 极角相同的两个半平面,保留左边
tt -- ;
if (OnLeft(q[tt], L[i].p)) q[tt] = L[i];
}
if (hh < tt) p[tt - 1] = Cross_point(q[tt - 1], q[tt]); // 计算队尾半平面交点
}
while (hh < tt && !OnLeft(q[hh], p[tt - 1])) tt -- ;
if (tt - hh <= 1) return {};
p[tt] = Cross_point(q[tt], q[hh]); // 计算队尾半平面交点
return vector<Point>(p.begin() + hh, p.begin() + tt + 1);
}
*/
struct Circle { // 定义圆
Point c;
DB r;
Circle() {}
Circle(const Point &c, DB r = 0) : c(c), r(r) {}
Point Oxy(DB a) { // 通过圆心角求坐标
return Point(c.x + cos(a) * r, c.y + sin(a) * r);
}
};
int Point_circle_relation(const Point &p, const Circle &C) { // 点与圆的关系
DB dist = Distance(p, C.c);
if (sgn(dist - C.r) < 0) return 0; // 点在圆内
if (sgn(dist - C.r) == 0) return 1; // 点在圆上
return 2; // 点在圆外
}
int Line_circle_relation(const Line v, const Circle &C) { // 直线和圆的关系
DB dist = Dis_point_line(C.c, v);
if (sgn(dist - C.r) < 0) return 0; // 直线和圆相交
if (sgn(dist - C.r) == 0) return 1; // 直线和圆相切
return 2; // 直线在圆外
}
int Seg_circle_relation(const Segment &v, const Circle &C) { //线段和圆的关系
DB dist = Dis_point_seg(C.c, v);
if (sgn(dist - C.r) < 0) return 0; // 线段在圆内
if (sgn(dist - C.r) == 0) return 1; // 线段与圆相切
return 2; // 线段在圆外
}
int Circle_circle_relation(const Circle &c1, const Circle &c2) { // 圆和圆的关系
DB dist = Distance(c1.c, c2.c);
if (sgn(dist) == 0) {
if (c1.r == c2.r) return -1; // 两圆重合
return 0; // 两圆圆心相同
}
if (dcmp(dist, c1.r + c2.r) == 0) return 1; // 两圆相切
if (dcmp(dist, c1.r + c2.r) < 0) return 2; // 两圆相交
return 3; //两圆相离
}
Point Dot_circle(const Point &p, const Circle &C) { // 计算圆上到点p的最近点
Point u, v, c = C.c;
DB dist = Distance(c, p), r = C.r;
if (sgn(dist)) return p;
u.x = c.x + r * fabs(c.x - p.x) / dist;
u.y = c.y + r * fabs(c.y - p.y) / dist * sgn((c.x - p.x) * (c.y - p.y));
v.x = c.x - r * fabs(c.x - p.x) / dist;
v.y = c.y - r * fabs(c.y - p.y) / dist * sgn((c.x - p.x) * (c.y - p.y));
return Distance(u, p) < Distance(v, p) ? u : v;
}
// 返回直线与圆的交点(线段的话判断点是否在线段上即可)
vector<Point> Line_cross_circle(const Line &v, const Circle &C) {
if (Line_circle_relation(v, C) == 2) return {};
Point q = Point_line_proj(C.c, v); // 圆心在直线上的投影
DB d = Dis_point_line(C.c, v), k = hypot(C.r, d), len = sqrt(C.r * C.r - d * d); // 圆心到直线的距离
if (sgn(k) == 0) return {q};
// if (!Point_on_segment(q, v)) {
// DB mind = min(Distance(C.c, v.p1), Distance(C.c, v.p2));
// if(dcmp(C.r, mind) <= 0) return {};
// }
return {q + Norm(v.p1 - v.p2) * len, q + Norm(v.p2 - v.p1) * len};
}
Point Circle_center(const Point &A, const Point &B, const Point &C) { //返回三角形垂心(外接圆圆心)
Vector AB = B - A, AC = C - A;
DB lenb = (AB & AB) / 2, lenc = (AC & AC) / 2, d = AB & AC;
return A + Point(lenb * AC.y - lenc * AB.y, lenc * AB.x - lenb * AC.x) / d;
}
// 返回三角形内接圆
Circle Circle_center_incircle(const Point &p1, const Point &p2, const Point &p3) {
DB a = Len(p2 - p3), b = Len(p3 - p1), c = Len(p1 - p2);
Point p = (p1 * a + p2 * b + p3 * c) / (a + b + c);
return Circle(p, Dis_point_line(p, Line(p1, p2)));
}
Circle Min_cover_circle(vector<Point> &p) {
random_shuffle(p.begin(), p.end());
Point c = p[0];
DB r = 0.0;
for (int i = 1; i < size(p); ++ i) {
if (sgn(Distance(p[i], c) - r) > 0) {
c = p[i], r = 0.0;
for (int j = 0; j < i; ++ j) {
if (sgn(Distance(p[j], c) - r) > 0) {
c = (p[i] + p[j]) / 2;
r = Distance(p[j], c);
for (int k = 0; k < j; ++ k) {
if (sgn(Distance(p[k], c) - r) > 0) {
c = Circle_center(p[i], p[j], p[k]);
r = Distance(p[i], c);
}
}
}
}
}
}
return Circle(c, r);
}
// 返回两圆交点
vector<Point> Circle_circle_intersection(Circle &c1, Circle &c2) {
DB d = Len(c1.c - c2.c);
if (sgn(d) == 0) return {}; // 可能出现两圆重合需特判
if (sgn(c1.r + c2.r - d) < 0 || sgn(fabs(c1.r - c2.r) - d) > 0) return {}; //相离或包含
Point c = c2.c - c1.c;
DB a = atan2(c.y, c.x);
DB da = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
Point p1 = c1.Oxy(a - da), p2 = c1.Oxy(a + da);
if (p1 == p2) return vector<Point>{p1};
return vector<Point>{p1, p2};
}
// 点到圆的切线
vector<Point> Point_circle_tangents(const Point &p, const Circle &C) {
Vector u = C.c - p;
DB dist = Len(u);
if (dist < C.r) return {};
else if (sgn(dist - C.r) == 0) {
return vector<Point>{Rotate(u, pi / 2)};
} else {
DB ang = asin(C.r / dist);
return vector<Point>{Rotate(u, -ang), Rotate(u, ang)};
}
}
// 两圆的公切线
int Circle_circle_tangents(Circle &A, Circle &B, vector<Point> &a, vector<Point> &b) {
if (A.r < B.r) swap(A, B), swap(a, b);
DB d2 = Len2(A.c - B.c), rdiff = A.r - B.r, rsum = A.r + B.r;
if (dcmp(d2, rdiff + rsum) < 0) return 0; // 内含
DB base = atan2(B.c.y - A.c.y, B.c.x - A.c.x);
if (d2 == 0 && A.r == B.r) return -1; // 无限多条切线
if (dcmp(d2, rdiff * rdiff) == 0) {
a.push_back(A.Oxy(base)), b.push_back(B.Oxy(base));
return 1;
}
DB ang = acos(rdiff / sqrt(d2));
a.push_back(A.Oxy(base + ang)), b.push_back(B.Oxy(base + ang));
a.push_back(A.Oxy(base - ang)), b.push_back(B.Oxy(base - ang));
if (dcmp(d2, rsum * rsum) == 0) {
a.push_back(A.Oxy(base)), b.push_back(B.Oxy(pi + base));
} else if (dcmp(d2, rsum * rsum) > 0) {
DB ang = acos((A.r + B.r) / sqrt(d2));
a.push_back(A.Oxy(base + ang)), b.push_back(B.Oxy(pi + base + ang));
a.push_back(A.Oxy(base - ang)), b.push_back(B.Oxy(pi + base - ang));
}
return size(a);
}
//两圆相交面积
DB Circle_circle_Area(const Circle &c1, const Circle &c2) {
DB d = Len(c1.c - c2.c);
if (dcmp(c1.r + c2.r, d) <= 0) return 0;
if (dcmp(d, fabs(c1.r - c2.r)) <= 0) return pi * min(c1.r, c2.r) * min(c1.r, c2.r);
DB x = (d * d + c1.r * c1.r - c2.r * c2.r) / (2 * d);
DB p = (c1.r + c2.r + d) / 2;
DB t1 = acos(x / c1.r), t2 = acos((d - x) / c2.r);
DB s1 = c1.r * c1.r * t1, s2 = c2.r * c2.r * t2, s3 = 2 * sqrt(p * (p - c1.r) * (p - c2.r) * (p - d));
return s1 + s2 - s3;
}
// 返回扇形面积
DB Sector_area(const Circle &C, const Point &a, const Point &b) {
DB angle = Angle(a - C.c, b - C.c);
if (sgn(a ^ b) < 0) angle = -angle;
return C.r * C.r * angle / 2;
}
// 圆和三角形相交面积
DB Circle_triangle_area(const Circle &C, const Point &a, const Point &b) {
auto da = Distance(C.c, a), db = Distance(C.c, b);
if (dcmp(C.r, da) >= 0 && dcmp(C.r, db) >= 0) return (a ^ b) / 2;
if (!sgn(a ^ b)) return 0;
auto p = Line_cross_circle(Line(a, b), C);
if (size(p) == 0) return Sector_area(C, a, b);
if (dcmp(C.r, db) >= 0) return (p[0] ^ b) / 2 + Sector_area(C, a, p[0]);
if (dcmp(C.r, da) >= 0) return (a ^ p[1]) / 2 + Sector_area(C, p[1], b);
return Sector_area(C, a, p[0]) + (p[0] ^ p[1]) / 2 + Sector_area(C, p[1], b);
}
struct Matrix {
Point p1, p2;
Matrix() {}
Matrix(const Point &p1, const Point &p2) : p1(p1), p2(p2) {}
};
// 扫描线求矩形面积并
DB Atlantis(const vector<Matrix> &mt) {
vector<DB> ver;
int n = size(mt);
for (int i = 0; i < n; ++ i) {
ver.push_back(mt[i].p1.x);
ver.push_back(mt[i].p2.x);
}
sort(ver.begin(), ver.end());
ver.erase(unique(ver.begin(), ver.end()), ver.end());
DB area = 0;
for (int i = 1; i < size(ver); ++ i) {
int x1 = ver[i - 1], x2 = ver[i];
vector<pair<DB, DB>> strip;
for (int j = 0; j < n; ++ j) {
if (mt[j].p1.x <= x1 && mt[j].p2.x >= x2) {
strip.push_back({mt[j].p1.y, mt[j].p2.y});
}
}
sort(strip.begin(), strip.end());
DB len = 0;
for (int j = 0, k = -1e9; j < size(strip); ++ j) {
if (strip[j].second >= k) {
len += strip[j].second - max((DB)k, strip[j].first);
k = strip[j].second;
}
}
area += len * (x2 - x1);
}
return area;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
std::vector<Point> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++i) {
std::cout << a[i] << " \n"[i == n - 1];
}
}
三维
#include <bits/stdc++.h>
using namespace std;
using DB = double;
const DB eps = 1e-12, inf = 1e100, pi = acos(-1);
DB dcmp(DB x, DB y) { return fabs(x - y) < eps ? 0 : x < y ? -1 : 1; }
DB sgn(DB x) { return fabs(x) < 0 ? 0 : x < 0 ? -1 : 1; }
DB rand_eps() { return ((DB)rand() / RAND_MAX - 0.5) * eps; }
struct Point3 {
DB x, y, z;
Point3 () {}
Point3 (DB x, DB y, DB z) : x(x), y(y), z(z) {}
void shake() { x += rand_eps(), y += rand_eps(), z += rand_eps(); }
Point3 operator+(const Point3 &P) const {
return Point3(x + P.x, y + P.y, z + P.z);
}
Point3 operator-(const Point3 &P) const {
return Point3(x - P.x, y - P.y, z - P.z);
}
Point3 operator*(DB p) const { return Point3(x * p, y * p, z * p); }
Point3 operator/(DB p) const { return Point3(x / p, y / p, z / p); }
DB operator&(const Point3 &P) const { return x * P.x + y * P.y + z * P.z; }
Point3 operator^(const Point3 &P) const {
return Point3(y * P.z - z - P.y, z * P.x - x * P.z, x * P.y - y * P.x);
}
friend istream &operator>>(istream &is, Point3 &rhs) {
return is >> rhs.x >> rhs.y >> rhs.z;
}
friend ostream &operator<<(ostream &os, const Point3 &rhs) {
return os << '(' << rhs.x << ',' << rhs.y << ',' << rhs.z << ')';
}
};
using Vector3 = Point3;
bool cmp(const Point3 &a , const Point3 &b) {
if (a.x == b.x) {
if (a.y == b.y) { return a.z < b.z; }
else { return a.y < b.y; }
} else { return a.x < b.x; }
}
// 最近点对
DB Closest_pair(const vector<Point3> &p, int l, int r) {
DB dist = inf;
if (l == r) return dist;
if (l + 1 == r) return Distance(p[l], p[r]);
int mid = l + r >> 1;
DB d1 = Closest_pair(p, l, mid), d2 = Closest_pair(p, mid + 1, r);
dist = min(d1, d2);
vector<Point3> tmp;
for (int i = l; i <= r; ++ i)
if (fabs(p[mid].x - p[i].x) <= dist) tmp.push_back(p[i]);
for (int i = 0; i < tmp.size(); ++ i)
for (int j = i + 1; j < tmp.size(); ++ j) {
dist = min(dist, Distance(tmp[i], tmp[j]));
}
return dist;
}
DB Len(const Vector3 &A) { return sqrt(A & A); }
DB Len2(const Vector3 &A) { return A & A; }
DB Distance(const Vector3 &A, const Vector3 &B) { return Len(A - B); }
// 三角形面积的两倍
DB Area2(const Point3 &A, const Point3 &B, const Point3 &C) { return Len(B - A ^ C - A); }
DB Angle(const Vector3 &A, const Vector3 &B) { return acos((A & A) / Len(A) / Len(B)); }
// 判断点是否在三角形中
bool Point_in_triangle(const Point3 &A, const Point3 &B, const Point3 &C, const Point3 p) {
return dcmp(Area2(p, A, B) + Area2(p, A, C) + Area2(p, B, C), Area2(A, B, C)) == 0;
}
// 四面体有向面积*6
DB volume4(const Point3 &A, const Point3 &B, const Point3 &C, const Point3 &D) {
return ((B - A) ^ (C - A)) & (D - A);
}
// 四面体的重心
Point3 Centroid(const Point3 &A, const Point3 &B, const Point3 &C, const Point3 &D) {
return (A + B + C + D) / 4.0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用