比赛链接
F , G \rm{F, G} F,G 太难了,什么 S A M \rm{SAM} SAM 已经超出我的嫩力范围了,题解就不写了(
A. oiiaioiiiai
题目大意
共 T T T 组数据。要求构造一个长度为 n n n 的排列 p p p,使得最小化下式 ∑ i = 1 n ∑ j = i n ∏ k = i j p k . \sum\limits_{i = 1}^{n}\sum\limits_{j = i}^{n}\prod\limits_{k = i}^{j}p_k. i=1∑nj=i∑nk=i∏jpk. 如果有多个满足的,输出 字典序最小 的。
数据范围
- 1 ≤ ∑ n ≤ 1 0 6 . 1 \leq \sum n \leq 10^6. 1≤∑n≤106.
Solution
说实话不会证明,只会感觉,赛时猜了 5 5 5 次都暴毙了,后来直接打表出结果。
简单来说就是:根据组合数,越中间的数被乘的也越多,所以要从小到大填数,从中间往两边延伸。
- 若 n n n 是奇数,那就两个两个填,不断变换方向,最后一个数直接填。例如 4 , 3 , 1 , 2 , 5 4, 3, 1, 2, 5 4,3,1,2,5 就是从最中间开始, 1 , 2 1, 2 1,2 往右走, 3 , 4 3, 4 3,4 往左走,然后最后剩个 5 5 5;
- 若 n n n 是偶数,那就在中间偏左一个或偏右一个位置填 1 1 1,然后两个两个填,不断变化方向,最后一个数直接填。例如 8 , 5 , 4 , 1 , 2 , 3 , 6 , 7 8, 5, 4, 1, 2, 3, 6, 7 8,5,4,1,2,3,6,7 或 7 , 6 , 3 , 2 , 1 , 4 , 5 , 8 7, 6, 3, 2, 1, 4, 5, 8 7,6,3,2,1,4,5,8,就是 1 1 1 首先占据中心位置,然后开始 2 , 3 2, 3 2,3 一组, 4 , 5 4, 5 4,5 一组,一直下去,直到剩 8 8 8 一个。
时间复杂度 O ( ∑ n ) O(\sum n) O(∑n)
C++ Code (my implementation)
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template<class T, size_t L>
std::ostream &operator<<(std::ostream &os, const std::array<T, L> &a) {
os << a[0] + 1;
for (size_t i = 1; i < L; i++) {
os << " " << a[i] + 1;
}
return os;
}
template<class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
os << v[0];
for (size_t i = 1; i < v.size(); i++) {
os << " " << v[i];
}
return os;
}
void solve() {
int n;
std::cin >> n;
int m = (n - 1) / 2;
std::vector<int> ans0(n);
std::vector<int> ans1(n);
if (n % 2 == 0) {
ans0[m + 1] = 1;
for (int i = m, cnt = 1, x = 2, sgn = -1, d = 3; cnt < n; cnt += 2, d += 2, sgn = -sgn) {
if (cnt + 1 == n) {
ans0[i] = x;
break;
}
ans0[i] = x++;
ans0[i + sgn] = x++;
i = (i + sgn) - sgn * d;
}
ans1[m] = 1;
for (int i = m + 1, cnt = 1, x = 2, sgn = 1, d = 3; cnt < n; cnt += 2, d += 2, sgn = -sgn) {
if (cnt + 1 == n) {
ans1[i] = x;
break;
}
ans1[i] = x++;
ans1[i + sgn] = x++;
i = (i + sgn) - sgn * d;
}
} else {
for (int i = m, cnt = 0, x = 1, sgn = 1, d = 2; cnt < n; cnt += 2, d += 2, sgn = -sgn) {
if (cnt + 1 == n) {
ans0[i] = x;
break;
}
ans0[i] = x++;
ans0[i + sgn] = x++;
i = (i + sgn) - sgn * d;
}
for (int i = m, cnt = 0, x = 1, sgn = -1, d = 2; cnt < n; cnt += 2, d += 2, sgn = -sgn) {
if (cnt + 1 == n) {
ans1[i] = x;
break;
}
ans1[i] = x++;
ans1[i + sgn] = x++;
i = (i + sgn) - sgn * d;
}
}
std::cout << std::min(ans0, ans1) << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
// std::cerr << " -------------- init ----------------- \n";
// constexpr int N = 10;
// std::array<int, N> ord{};
// i64 ans = std::numeric_limits<i64>::max() / 2;
// std::ranges::iota(ord, 0);
// do {
// i64 res = 0;
// for (int i = 0; i < N; i++) {
// for (int j = i + 1; j <= N; j++) {
// int mul = 1;
// for (int k = i; k < j; k++) {
// mul *= ord[k] + 1;
// }
// res += mul;
// }
// }
// ans = std::min(ans, res);
// } while (std::next_permutation(ord.begin(), ord.end()));
// std::ranges::iota(ord, 0);
// do {
// i64 res = 0;
// for (int i = 0; i < N; i++) {
// for (int j = i + 1; j <= N; j++) {
// int mul = 1;
// for (int k = i; k < j; k++) {
// mul *= ord[k] + 1;
// }
// res += mul;
// }
// }
// if (ans == res) {
// std::cerr << ord << "\n";
// }
// } while (std::next_permutation(ord.begin(), ord.end()));
// std::cerr << " -------------- end init -------------- \n";
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
简单实现:先填 n n n,然后反着两个两个来就好了 🤣
C++ Code (easy implementation)
#include <bits/stdc++.h>
template<class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
os << v[0];
for (size_t i = 1; i < v.size(); i++) {
os << " " << v[i];
}
return os;
}
void solve() {
int n;
std::cin >> n;
std::vector<int> ans(n, n);
int l = 0;
int r = n - 1;
int x = n - 1;
for (int i = 0; x > 0; i = (i + 1) % 4) {
if (i < 2) {
ans[l++] = x--;
} else {
ans[--r] = x--;
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
B. 超自然研的一天
题目大意
给定一个长度为 n n n 的非负整数数组 a a a 以及一个操作次数 m m m。每个操作都会给出 l , r , k l, r, k l,r,k,表示要 a i : = a i + k , ∀ i ∈ [ l , r ] , a_i := a_i + k, \ \forall i \in [l, r], ai:=ai+k, ∀i∈[l,r], 保证任意时刻都有 a i ≥ 0 a_i \geq 0 ai≥0。
现在顺序做这 m m m 次操作。在第 o ∈ [ 1 , m ] o \in [1, m] o∈[1,m] 次操作做完之后, A l i c e \rm{Alice} Alice 想要知道,它至少要准备多大的非负整数 y y y,令某个 a i : = a i − y a_i := a_i - y ai:=ai−y 后(必须保证 a i ≥ 0 a_i \geq 0 ai≥0),才能存在一个正偶数 x x x,使得 a i > 0 , i ∈ [ 1 , x ) a_i > 0, \ i \in [1,x) ai>0, i∈[1,x) 且 a x = 0 a_x = 0 ax=0;如不存在输出 − 1 -1 −1。
数据范围
- 1 ≤ n , m ≤ 5 ⋅ 1 0 5 , 1 \leq n, m \leq 5 \cdot 10^5, 1≤n,m≤5⋅105,
- 0 ≤ a i ≤ 1 0 9 , 0 \leq a_i \leq 10^9, 0≤ai≤109,
- 1 ≤ l ≤ r ≤ n , 1 \leq l \leq r \leq n, 1≤l≤r≤n,
- 0 ≤ ∣ k ∣ ≤ 1 0 9 . 0 \leq |k| \leq 10^9. 0≤∣k∣≤109.
Solution
有区间操作和找 m e x \rm{mex} mex 操作,很容易想到线段树。
我们维护两棵线段树 s g t A , s g t E \rm{sgtA, sgtE} sgtA,sgtE。
- s g t A \rm{sgtA} sgtA 表示正常的线段树,维护整个数组 a a a 的值;
- s g t E \rm{sgtE} sgtE 表示只维护偶数下标 a i a_i ai 值的线段树,奇数下标的位置都设为 + ∞ +\infty +∞,用来求前缀最小值。
当在两棵线段树上做完第 o ∈ [ 1 , m ] o \in [1, m] o∈[1,m] 次操作时,我们用 线段树二分 在 s g t A \rm{sgtA} sgtA 上找到最左侧的 p p p,使得 a p = 0 a_p = 0 ap=0。如果找不到这样的 p p p,那就令 p = n + 1 p = n + 1 p=n+1。
- 如果 p p p 为偶数,那么直接输出 0 0 0,表示已经满足条件。
- 否则在 s g t E \rm{sgtE} sgtE 上找 [ 1 , p ) [1, p) [1,p) 之间的最小值 min \min min, y = min y = \min y=min 即可。
注:作者是 0-indexed
,所以代码中用的是
s
g
t
O
\rm{sgtO}
sgtO,表示维护奇数下标的
a
i
a_i
ai 值,这与上面的思路略有不同,但也只要翻转一下奇偶就可以了。
时间复杂度 O ( ( n + m ) log n ) O(\rm{(n + m)\log n}) O((n+m)logn)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
for (auto &x: v) {
is >> x;
}
return is;
}
template<class Info, class Tag>
struct LazySegmentTree {
#define l(p) (p << 1)
#define r(p) (p << 1 | 1)
int n;
std::vector<Info> info;
std::vector<Tag> tag;
LazySegmentTree() {}
LazySegmentTree(int _n, Info _v = Info()) {
init(_n, _v);
}
template<class T>
LazySegmentTree(std::vector<T> _init) {
init(_init);
}
void init(int _n, Info _v = Info()) {
init(std::vector(_n, _v));
}
template<class T>
void init(std::vector<T> _init) {
n = _init.size();
info.assign(4 << std::__lg(n), Info());
tag.assign(4 << std::__lg(n), Tag());
auto build = [&](auto self, int p, int l, int r) {
if (r - l == 1) {
info[p] = _init[l];
return;
}
int m = l + r >> 1;
self(self, l(p), l, m);
self(self, r(p), m, r);
pull(p);
};
build(build, 1, 0, n);
}
void pull(int p) {
info[p] = info[l(p)] + info[r(p)];
}
void apply(int p, const Tag &v, int len) {
info[p].apply(v, len);
tag[p].apply(v);
}
void push(int p, int len) {
apply(l(p), tag[p], len / 2);
apply(r(p), tag[p], len - len / 2);
tag[p] = Tag();
}
void modify(int p, int l, int r, int x, const Info &v) {
if (r - l == 1) {
info[p] = v;
return;
}
int m = l + r >> 1;
push(p, r - l);
if (x < m) {
modify(l(p), l, m, x, v);
} else {
modify(r(p), m, r, x, v);
}
pull(p);
}
void modify(int p, const Info &v) {
modify(1, 0, n, p, v);
}
Info query(int p, int l, int r, int x, int y) {
if (l >= y or r <= x) {
return Info();
}
if (l >= x and r <= y) {
return info[p];
}
int m = l + r >> 1;
push(p, r - l);
return query(l(p), l, m, x, y) + query(r(p), m, r, x, y);
}
Info query(int l, int r) {
return query(1, 0, n, l, r);
}
void Apply(int p, int l, int r, int x, int y, const Tag &v) {
if (l >= y or r <= x) {
return;
}
if (l >= x and r <= y) {
apply(p, v, r - l);
return;
}
int m = l + r >> 1;
push(p, r - l);
Apply(l(p), l, m, x, y, v);
Apply(r(p), m, r, x, y, v);
pull(p);
}
void Apply(int l, int r, const Tag &v) {
return Apply(1, 0, n, l, r, v);
}
template<class F>
int findFirst(int p, int l, int r, int x, int y, F &&pred) {
if (l >= y or r <= x) {
return -1;
}
if (l >= x and r <= y and !pred(info[p])) {
return -1;
}
if (r - l == 1) {
return l;
}
push(p, r - l);
int m = l + r >> 1;
int res = findFirst(l(p), l, m, x, y, pred);
if (res == -1) {
res = findFirst(r(p), m, r, x, y, pred);
}
return res;
}
template<class F>
int findFirst(int l, int r, F &&pred) {
return findFirst(1, 0, n, l, r, pred);
}
template<class F>
int findLast(int p, int l, int r, int x, int y, F &&pred) {
if (l >= y or r <= x) {
return -1;
}
if (l >= x and r <= y and !pred(info[p])) {
return -1;
}
if (r - l == 1) {
return l;
}
push(p, r - l);
int m = l + r >> 1;
int res = findLast(r(p), m, r, x, y, pred);
if (res == -1) {
res = findLast(l(p), l, m, x, y, pred);
}
return res;
}
template<class F>
int findLast(int l, int r, F &&pred) {
return findLast(1, 0, n, l, r, pred);
}
#undef l(p)
#undef r(p)
};
constexpr i64 inf = 1E18;
struct Tag {
i64 add = 0;
void apply(const Tag &t) {
add += t.add;
}
};
struct Info {
i64 min = inf;
void apply(const Tag &t, int len) {
min += t.add;
}
};
Info operator+(const Info &a, const Info &b) {
return {std::min(a.min, b.min)};
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
std::cin >> a;
LazySegmentTree<Info, Tag> sgtA(n);
LazySegmentTree<Info, Tag> sgtO(n);
for (int i = 0; i < n; i++) {
sgtA.modify(i, {a[i]});
if (i & 1) {
sgtO.modify(i, {a[i]});
}
}
for (int i = 0; i < m; i++) {
int l, r;
i64 k;
std::cin >> l >> r >> k;
l--;
sgtA.Apply(l, r, {k});
sgtO.Apply(l, r, {k});
int p = sgtA.findFirst(0, n, [&](const Info &info) {
return info.min == 0;
});
if (p == -1) {
p = n;
}
if (p & 1) {
std::cout << 0 << "\n";
continue;
}
i64 min = sgtO.query(0, p).min;
std::cout << (min < inf / 2 ? min: -1) << "\n";
}
return 0;
}
C. 绘画糕手
题目大意
共 T T T 组数据。给定一个长度为 n n n 的字符串 s s s 和一个正整数 k k k,现在你可以做以下操作(二选一)若干次直到无法操作。
- 若 i > k i > k i>k 且 s i ≠ s i − k s_i \neq s_{i - k} si=si−k,令 s i = s i − k s_i = s_{i - k} si=si−k;
- 若 i + k ≤ n i + k \leq n i+k≤n 且 s i ≠ s i + k s_i \neq s_{i + k} si=si+k,令 s i = s i + k s_i = s_{i + k} si=si+k。
求 最少 要操作几次,才能无法继续操作。
数据范围
- 1 ≤ ∑ n ≤ 1 0 4 , 1 \leq \sum n \leq 10^4, 1≤∑n≤104,
- 1 ≤ k ≤ 1 0 9 , 1 \leq k \leq 10^9, 1≤k≤109,
- 保证 s s s 只含小写字母。
Solution
当 k ≥ n k \geq n k≥n 时显然已经无法操作。
当 k < n k < n k<n 时,我们相当于把 n n n 个字母分成了 k k k 组, 1 , 1 + k , 1 + 2 k , ⋯ 1, \ 1 + k, \ 1 + 2k, \cdots 1, 1+k, 1+2k,⋯ 一组, 2 , 2 + k , 2 + 2 k , ⋯ 2, \ 2 + k, \ 2 + 2k, \cdots 2, 2+k, 2+2k,⋯ 一组, ⋯ \cdots ⋯。组间互不干扰,所以我们只考虑每一组组内的最小操作次数。
对于某个组 [ r ] [r] [r](表示 r , r + k , r + 2 k , ⋯ r, \ r + k, \ r + 2k, \cdots r, r+k, r+2k,⋯)来说,可以选定其中出现次数最多的字符,然后把其他字符向两边扩展(即题中的操作),直到填满这个组。
答案就是各组最小值的和。
时间复杂度 O ( ∑ n ⋅ 26 ) O(\rm{\sum n \cdot 26}) O(∑n⋅26)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
void solve() {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
if (n <= k) {
std::cout << 0 << "\n";
return;
}
int ans = 0;
for (int r = 0; r < k; r++) {
std::array<int, 26> cnt{};
int tot = 0;
for (int i = r; i < n; i += k) {
cnt[s[i] - 'a']++;
tot++;
}
ans += tot - std::ranges::max(cnt);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
D. 亲爱的滋崩小姐
题目大意
在给定的 n n n 个数中选出 m m m 个数,使得这 m m m 个数两两互质并且这 m m m 个数的和最大。
数据范围
- 1 ≤ n , a i ≤ 1000. 1 \leq n, a_i \leq 1000. 1≤n,ai≤1000.
Solution
首先对于 a i = 1 a_i = 1 ai=1 我们可以全部取出来,因为 1 1 1 和任何数互质。下面仅考虑 a i > 1 a_i > 1 ai>1 的情况。
其实我们很容易会想到状压 d p \rm{dp} dp,不过 1000 1000 1000 以下的质数有 168 168 168 个,如果全部塞进状态里就是 2 168 2^{168} 2168 种情况,铁超时。
而对于这种情况,一定是有特殊性质需要发掘。这就涉及到质数题目中常见的思路:根号分治。
记 V = ⌊ 1000 ⌋ = 31 V = \lfloor \sqrt{1000} \rfloor = 31 V=⌊1000⌋=31,这是第 11 11 11 个质数。
具体来说,如果一个正整数 x ∈ [ 1 , 1000 ] x \in [1, 1000] x∈[1,1000],它的质因数分解为 x = p 1 α 1 p 2 α 2 ⋯ p k α k , x = p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_k^{\alpha_k}, x=p1α1p2α2⋯pkαk, 那么对于 p i ≤ V p_i \leq V pi≤V, α i \alpha_i αi 可能 ≥ 2 \geq 2 ≥2,而对于 p j > V p_j > V pj>V,一定有 α j = 1 \alpha_j = 1 αj=1。
我们不妨将不在 x x x 中的质因数也加进来,形成一个长度为 168 168 168 的二进制串,称其为 B ( x ) B(x) B(x)。
- 若 B ( x ) B(x) B(x) 第 i i i 位上为 1 1 1 表示有第 i i i 个质数作为质因子,为 0 0 0 表示没有。
质数表: 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 ⋯ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 \cdots 2,3,5,7,11,13,17,19,23,29,31⋯(仅写出前 11 11 11 个)。
例如 x = 109850 = 2 1 ⋅ 5 2 ⋅ 1 3 3 x = 109850 = 2^1 \cdot 5^2 \cdot 13^3 x=109850=21⋅52⋅133,则 B ( x ) = 10100100 ⋯ 00000 B(x) = 10100100 \cdots 00000 B(x)=10100100⋯00000(共 168 168 168 位)。
- 第 1 1 1 位是 1 1 1,含有第 1 1 1 个质数 2 2 2;
- 第 3 3 3 位是 1 1 1,含有第 3 3 3 个质数 5 5 5;
- 第 6 6 6 位是 1 1 1,含有第 6 6 6 个质数 13 13 13。
这样一来,从第 12 12 12 位到第 168 168 168 位,最多只可能出现一个 1 1 1。
也就是说我们 只要对前 11 11 11 个质数维护一个二进制掩码,对后面的大质数一个个枚举即可。
这样说可能有点晦涩,我们直接拿公式开始推导。
我们知道,二进制枚举的本质就是 组合枚举,因为 2 n = ∑ k = 0 n ( n k ) . 2^n = \sum\limits_{k = 0}^{n}{n \choose k}. 2n=k=0∑n(kn). 令 n = 168 n = 168 n=168。现在我们知道了第 12 12 12 位到第 168 168 168 位最多只可能出现一个 1 1 1,那不妨用 范德蒙德卷积 将 ( n k ) {n \choose k} (kn) 打开,即 ( n k ) = ∑ i = 0 k ( n − 11 i ) ( 11 k − i ) , {n \choose k} = \sum\limits_{i = 0}^{k}{n - 11 \choose i}{11 \choose k - i}, (kn)=i=0∑k(in−11)(k−i11), 而根据上面的推论, i i i 最多为 1 1 1,因此不需要枚举 ( n k ) {n \choose k} (kn) 种情况,只需要枚举 ∑ i = 0 1 ( n − 11 i ) ( 11 k − i ) . \sum\limits_{i = 0}^{1}{n - 11 \choose i}{11 \choose k - i}. i=0∑1(in−11)(k−i11). 同时又考虑到 k − i > 11 k - i > 11 k−i>11 的状态其实没用,因此 k k k 最大取到 12 12 12 就够了,即枚举的状态数量是 ∑ k = 0 12 ∑ i = 0 1 ( n − 11 i ) ( 11 k − i ) . \sum\limits_{k = 0}^{12}\sum\limits_{i = 0}^{1}{n - 11 \choose i}{11 \choose k - i}. k=0∑12i=0∑1(in−11)(k−i11). 接着我们将里面展开 ∑ k = 0 12 ( n − 11 0 ) ( 11 k ) + ∑ k = 0 12 ( n − 11 1 ) ( 11 k − 1 ) , \sum\limits_{k = 0}^{12}{n - 11 \choose 0}{11 \choose k} + \sum\limits_{k = 0}^{12}{n -11 \choose 1}{11 \choose k - 1}, k=0∑12(0n−11)(k11)+k=0∑12(1n−11)(k−111), 继续化简得到 ∑ k = 0 11 ( 11 k ) + ( n − 11 ) ∑ k = 0 11 ( 11 k ) . \sum\limits_{k = 0}^{11}{11 \choose k} + (n - 11)\sum\limits_{k = 0}^{11}{11 \choose k}. k=0∑11(k11)+(n−11)k=0∑11(k11).
- 这个式子的前半部分就是只考虑前 11 11 11 个质数的组合,也就是说只要 x x x 含有一个大质数( > V > V >V)就不参与计算;
- 后半部分的意思就是,枚举后 n − 11 n - 11 n−11 个大质数,将含有 p > V p > V p>V 质因子的 x x x 的贡献,加入到前 11 11 11 个质数的掩码中。
再简单一点,说人话就是:对于 x = ⋯ p 1 x = \cdots p^1 x=⋯p1(其中 p > V p > V p>V),只要把 p p p 从 x x x 中除掉,就可以回归到仅含前 11 11 11 个质数的状态。
所以最终的做法就是设 d p [ m s k ] \rm{dp[msk]} dp[msk] 表示前 11 11 11 个质数的被包含状态为 m s k \rm{msk} msk, m s k \rm{msk} msk 及其所有子集 能获得的最大和是 d p [ m s k ] \rm{dp[msk]} dp[msk]。一开始对那些仅含小质数的 x x x 做 d p \rm{dp} dp,像上两段说的那样;然后再枚举大质数做相同的 d p \rm{dp} dp 操作 n − 11 n - 11 n−11 次。
当然,为了知道大质数 p p p 对应了哪些 x x x,以及这个 x x x 除了 p p p 以外的前 11 11 11 个质数的掩码 m s k \rm{msk} msk,我们需要开 p a i r \rm{pair} pair ( m s k , x ) (\rm{msk, x}) (msk,x) 来存。
时间复杂度 O ( U log log U + n log n + n log U + n log n + n 2 N ) = O ( n 2 N ) O(\rm{U\log\log U + n\log n + n\log U + n\log n + n2^N}) = O(n2^N) O(UloglogU+nlogn+nlogU+nlogn+n2N)=O(n2N)
- 其中 U = 1000 , N = 11 , n ≤ 1000 U = 1000, \ N = 11, \ n \leq 1000 U=1000, N=11, n≤1000;
- U log log U U\log\log U UloglogU 是欧拉筛预处理前 1000 1000 1000 个质数;
- n log n n\log n nlogn 是对 a a a 数组排序去重;
- n log U n\log U nlogU 是算每个 a i a_i ai 的质因子;
- n log n n\log n nlogn 是最坏情况下每次都会加入一个大质数, m a p \rm{map} map 操作会自带 log \log log;
- n 2 N n2^N n2N 是仅对小质数和大质数进行 d p \rm{dp} dp;
- 实际上大部分根本跑不满,总的基本 5 m s \rm{5 ms} 5ms 就跑完了;
- 注意在下面的代码中 N N N 不是 11 11 11,而是 2 11 2^{11} 211。
C++ Code
#include <bits/stdc++.h>
std::vector<int> prime;
std::vector<int> minp;
void sieve(int n) {
minp.resize(n + 1);
prime.clear();
for (int i = 2; i <= n; i++) {
if (minp[i] == 0) {
prime.push_back(i);
minp[i] = i;
}
for (int p: prime) {
if (i * p > n) {
break;
}
minp[i * p] = p;
if (minp[i] == p) {
break;
}
}
}
}
constexpr int N = 1 << 11;
constexpr int V = std::sqrt(1000);
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
sieve(1000);
int n;
std::cin >> n;
int cnt = 0;
std::vector<int> a;
a.reserve(n);
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
if (x > 1) {
a.push_back(x);
} else {
cnt++;
}
}
if (cnt == n) {
std::cout << cnt << "\n";
return 0;
}
std::ranges::sort(a);
a.erase(std::unique(a.begin(), a.end()), a.end());
std::unordered_map<int, int> ump;
for (int i = 0; prime[i] <= V; i++) {
ump[prime[i]] = i;
}
std::array<int, N> dp{};
std::map<int, std::vector<std::pair<int, int>>> mp;
for (int x: a) {
int msk = 0;
int maxp = 0;
for (int y = x; y > 1; ) {
int p = minp[y];
if (p <= V) {
msk ^= 1 << ump[p];
} else {
maxp = p;
}
while (y % p == 0) {
y /= p;
}
}
if (maxp > V) {
mp[maxp].emplace_back(msk, x);
continue;
}
auto ndp = dp;
for (int nmsk = 1; nmsk < N; nmsk++) {
if ((nmsk & msk) == msk) {
ndp[nmsk] = std::max(ndp[nmsk], dp[nmsk ^ msk] + x);
}
}
dp = std::move(ndp);
}
for (const auto &[p, v]: mp) {
auto ndp = dp;
for (const auto &[msk, x]: v) {
for (int nmsk = 1; nmsk < N; nmsk++) {
if ((nmsk & msk) == msk) {
ndp[nmsk] = std::max(ndp[nmsk], dp[nmsk ^ msk] + x);
}
}
}
dp = std::move(ndp);
}
std::cout << cnt + dp.back() << "\n";
return 0;
}
E. 颂乐人偶的迷失
Solution
树的直径板子题,只要
b
f
s
\rm{bfs}
bfs 两次即可(jiangly
的写法)。
时间复杂度 O ( N ) O(N) O(N)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::vector<int> d;
auto bfs = [&](int S) {
d.assign(n, -1);
d[S] = 0;
std::queue<int> q;
q.push(S);
while (not q.empty()) {
int x = q.front(); q.pop();
for (int y: adj[x]) {
if (d[y] == -1) {
d[y] = d[x] + 1;
q.push(y);
}
}
}
return std::max_element(d.begin(), d.end()) - d.begin();
};
int x = bfs(0);
int y = bfs(x);
std::cout << d[y] << "\n";
return 0;
}
H. ウミユリ海底譚 Re:MASTER
题目大意(这个大意其实已经带有做法了)
共 T T T 组数据。每组给定两个正整数 n , p n, p n,p 以及 n n n 个整数对 a i , b i a_i, b_i ai,bi。
对于一个正整数 t t t,我们计算其价值 W ( t ) W(t) W(t) 如下:
- 一开始 s = 0 s = 0 s=0;
- 若 b i > t b_i > t bi>t,则 s : = 0 s := 0 s:=0,进入下一个整数对;
- 否则
b
i
≤
t
b_i \leq t
bi≤t,你可以对下面两种操作二选一;
- s : = s + a i , s := s + a_i, s:=s+ai,
- s : = s . s := s. s:=s.
- W ( t ) W(t) W(t) 就是这个全过程中 s s s 的最大值。
对每组数据,你需要确定一个最小的 t t t,使得 W ( t ) ≥ p W(t) \geq p W(t)≥p;如果不存在这样的 t t t,输出 − 1 -1 −1。
数据范围
- 1 ≤ ∑ n ≤ 1 0 6 , 1 \leq \sum n \leq 10^6, 1≤∑n≤106,
- 1 ≤ p ≤ 1 0 18 , 1 \leq p \leq 10^{18}, 1≤p≤1018,
- − 1 0 9 ≤ a i ≤ 1 0 9 , -10^9 \leq a_i \leq 10^9, −109≤ai≤109,
- 1 ≤ b i ≤ 1 0 9 . 1 \leq b_i \leq 10^9. 1≤bi≤109.
Solution
显然 t t t 具有二段性,可以二分。
在 c h e c k \rm{check} check 中直接按照上述的 题目大意 进行模拟。
时间复杂度 O ( ∑ n log max ( b i ) ) O(\rm{\sum n\log\max(b_i)}) O(∑nlogmax(bi))
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template<class T, class U>
std::istream &operator>>(std::istream &is, std::pair<T, U> &p) {
is >> p.first >> p.second;
return is;
}
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
for (auto &x: v) {
is >> x;
}
return is;
}
void solve() {
int n;
i64 p;
std::cin >> n >> p;
std::vector<std::pair<int, int>> note(n);
std::cin >> note;
int m = 0;
for (const auto &[a, b]: note) {
m = std::max(m, b);
}
auto calc = [&](int t) {
i64 max = 0;
i64 sum = 0;
for (const auto &[a, b]: note) {
if (b > t) {
sum = 0;
continue;
}
sum = std::max(0LL, sum + a);
max = std::max(max, sum);
}
return max;
};
int lo = 1, hi = m + 1;
while (lo + 1 < hi) {
int m = lo + hi >> 1;
if (calc(m) >= p) {
hi = m;
} else {
lo = m;
}
}
std::cout << (calc(hi) >= p ? hi: -1) << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
I. 薇儿丹蒂 Verthandi
Solution
直接排序即可。
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
template<class T, class U>
std::istream &operator>>(std::istream &is, std::pair<T, U> &p) {
is >> p.first >> p.second;
return is;
}
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
for (auto &x: v) {
is >> x;
}
return is;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int N;
std::cin >> N;
std::vector<std::pair<std::string, std::string>> ans(N);
std::cin >> ans;
std::ranges::sort(ans);
for (const auto &[s, v]: ans) {
std::cout << s << " " << v << "\n";
}
return 0;
}
J. 都是绘绘,都是早凉的翅膀
Solution
这是个结论题。
在原始的 N i m \rm{Nim} Nim 游戏中,谁不能拿石子谁失败;而本题中是 谁最后一个拿石子谁失败。
原始的结论是:所有 a i a_i ai 的异或和为 0 0 0 则先手必胜。
那么我们很容易想到,本题的结论 会不会是:所有 a i a_i ai 的异或和不为 0 0 0 则先手必胜?
事实上部分正确,但是需要 特判一种情况:即 ∀ a i = 1 \forall a_i = 1 ∀ai=1 的情况;这种情况需要看 n n n 的奇偶,显然 n n n 为偶数时先手必胜。
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
for (auto &x: v) {
is >> x;
}
return is;
}
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
std::cin >> a;
int m = std::ranges::max(a);
if (m == 1) {
std::cout << (n & 1 ? "Jiuhui\n": "Akie\n");
return;
}
int s = std::accumulate(a.begin(), a.end(), 0, [&](int s, int x) {
return s ^ x;
});
std::cout << (s > 0 ? "Akie\n": "Jiuhui\n");
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
K. 谁改了我键位
Solution
直接全排列 4 ! 4! 4! 模拟 w a s d \rm{wasd} wasd 的键位分布,但是这里 n n n 最大是 1 0 7 10^7 107,如果是 4 ! ⋅ 1 0 7 4! \cdot 10^7 4!⋅107 肯定超时,所以需要 O ( n ) O(n) O(n) 预处理 ( 4 2 ) = 6 {4 \choose 2} = 6 (24)=6 对字符的出现次数,然后复杂度就是 O ( n + 4 ! ⋅ 4 2 ) O(n + 4! \cdot 4^2) O(n+4!⋅42)。
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
std::unordered_map<char, int> ump {
{'a', 0},
{'s', 1},
{'d', 2},
{'w', 3}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::array<std::array<int, 4>, 4> dis{};
std::array<std::array<int, 4>, 4> cnt{};
for (int i = 1; i < n; i++) {
cnt[ump[s[i - 1]]][ump[s[i]]]++;
}
int ans = n * 4;
std::array<int, 4> ord{};
std::iota(ord.begin(), ord.end(), 0);
do {
dis[ord[0]][ord[1]] = dis[ord[1]][ord[0]] = 2;
dis[ord[0]][ord[2]] = dis[ord[2]][ord[0]] = 1;
dis[ord[0]][ord[3]] = dis[ord[3]][ord[0]] = 2;
dis[ord[1]][ord[2]] = dis[ord[2]][ord[1]] = 1;
dis[ord[1]][ord[3]] = dis[ord[3]][ord[1]] = 2;
dis[ord[2]][ord[3]] = dis[ord[3]][ord[2]] = 1;
int res = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
res += cnt[i][j] * dis[i][j];
}
}
ans = std::min(ans, res);
} while (std::next_permutation(ord.begin(), ord.end()));
std::cout << ans << "\n";
return 0;
}
L. 谁是MVP
Solution
签到题,直接判断。
Python Code
n, m = map(int, input().split())
if n > m:
print("Alice")
else:
print("Bob")
M. 重生之我是明日方舟设计师
题目大意( l i t e r a l l y h a r d ! \rm\pmb{literally \ hard \ !} literally hard !)
共 n n n 组点集,其中第 i i i 组点集包含 m i m_i mi 个二维点 ( x i j , y i j ) (x_{ij}, y_{ij}) (xij,yij) 以及权值 k i k_i ki。现在把这 n n n 组点集按照任意顺序分配到 a , b a, b a,b 两个数组中。
对于一个长度为 l l l 的数组 t t t,我们定义 W ( t ) W(t) W(t) 如下:
- H i H_i Hi 表示 P t 1 ∪ P t 2 ∪ ⋯ ∪ P t i P_{t_1} \cup P_{t_2} \cup \cdots \cup P_{t_i} Pt1∪Pt2∪⋯∪Pti 的所有二维点形成的凸包或退化多边形,面积为 S i S_i Si;
- 那么 W ( t ) = ∑ i = 1 l 2 ⋅ k i ⋅ ( S i − S i − 1 ) W(t) = \sum\limits_{i = 1}^{l}2 \cdot k_i \cdot (S_i - S_{i - 1}) W(t)=i=1∑l2⋅ki⋅(Si−Si−1) 特别的 S 0 = 0 S_0 = 0 S0=0。
求 W ( a ) + W ( b ) W(a) + W(b) W(a)+W(b) 的最大值。
数据范围
- 1 ≤ n ≤ 15 , 1 \leq n \leq 15, 1≤n≤15,
- 1 ≤ m i ≤ 500 , 1 \leq m_i \leq 500, 1≤mi≤500,
- 1 ≤ k i ≤ 1 0 5 . 1 \leq k_i \leq 10^5. 1≤ki≤105.
Solution
可以发现,将点集分给 a a a 或 b b b 的情况我们可以统一考虑为只分给 a a a 的情况,剩下的另一半全部分给 b b b 一定最优。
设 d p [ m s k ] \rm{dp[msk]} dp[msk] 表示选择 m s k \rm{msk} msk 这个集合加入 a a a,其他的加入 b b b。状态转移只要算不在 m s k \rm{msk} msk 中的点集 P i P_i Pi,其贡献可以暴力求一遍凸包来计算。
下面阐述关于凸包的两个计算知识点。
首先是 鞋带定理。
它描述了一个已知 n n n 个(顺时针或逆时针的)多边形外围点,如何求这个多边形的面积,其实就是做 n n n 个叉乘再相加,最后取绝对值(本题不需要除以 2 2 2,因为题目要求的式子里有 2 2 2 倍)。
其次是 单调链算法。
它介绍了如何寻找一个点集的凸包,分成两部分,下半凸包和上半凸包。找的逻辑都是一样的:
- 就是行进的方向一定是逆时针严格向左,即叉乘要正负号保持一致。
当然这里它对点的排序是按照 纵坐标横坐标升序排列 的,而不是 极角排序。
- 注:这里如果你是对横坐标先排序,那么极角排序的极轴应该换成 y y y 轴负半轴,详见 洛谷 P8101 [USACO22JAN] Multiple Choice Test P。
我代码中默认的小于号重载就是极角排序。
时间复杂度 O ( 2 n n m log ( n m ) ) O(\rm{2^n nm\log (nm)}) O(2nnmlog(nm))
- 实际上根本跑不满,不过常数确实会稍大,所以一般会跑到 500 m s \rm{500 \ ms} 500 ms 往上。
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;
constexpr f64 pi = std::numbers::pi;
struct Point {
int x, y;
constexpr Point(): x{}, y{} {}
constexpr Point(int _x, int _y): x{_x}, y{_y} {}
constexpr Point(const Point &p): x{p.x}, y{p.y} {}
constexpr void reduce() {
int d = std::gcd(x, y);
if (d != 0) {
x /= d;
y /= d;
}
}
constexpr void reset(int nx = 0, int ny = 0) {
x = nx;
y = ny;
}
constexpr static int area(Point a) {
if (a.x > 0 and a.y >= 0) {
return 1;
} else if (a.x <= 0 and a.y > 0) {
return 2;
} else if (a.x < 0 and a.y <= 0) {
return 3;
}
return 4;
}
constexpr int area() const {
return area(*this);
}
constexpr static double angle(Point a) {
return atan2(a.y, a.x);
}
constexpr double angle() const {
return angle(*this);
}
constexpr Point rotate(int mode = 0) {
if (mode) {
std::tie(x, y) = std::pair {y, -x};
} else {
std::tie(x, y) = std::pair {-y, x};
}
return *this;
}
constexpr Point &operator+=(Point rhs) & {
x += rhs.x;
y += rhs.y;
return *this;
}
constexpr Point &operator-=(Point rhs) & {
x -= rhs.x;
y -= rhs.y;
return *this;
}
friend constexpr Point operator+(Point lhs, Point rhs) {
Point res = lhs;
res += rhs;
return res;
}
friend constexpr Point operator-(Point lhs, Point rhs) {
Point res = lhs;
res -= rhs;
return res;
}
constexpr Point operator*=(int k) & {
// assert(k != 0);
x *= k;
y *= k;
return *this;
}
friend constexpr Point operator*(Point lhs, int k) {
Point res = lhs;
res *= k;
return res;
}
friend constexpr Point operator*(int k, Point rhs) {
Point res = rhs;
res *= k;
return res;
}
friend constexpr i64 operator*(Point lhs, Point rhs) { // 内积
i64 res = 1LL * lhs.x * rhs.x + 1LL * lhs.y * rhs.y;
return res;
}
friend constexpr i64 operator^(Point lhs, Point rhs) { // 叉乘
i64 res = 1LL * lhs.x * rhs.y - 1LL * lhs.y * rhs.x;
return res;
}
constexpr static i64 Module(Point a) { // 到原点距离的平方
return a * a;
}
constexpr static double module(Point a) { // 到原点的距离
return std::sqrt(Module(a));
}
constexpr i64 Module() const { // 自身模长的平方
return Module(*this);
}
constexpr double module() const { // 自身模长
return module(*this);
}
constexpr static i64 Distance(Point lhs, Point rhs) { // 两点间距离的平方
return Module(rhs - lhs);
}
constexpr static double distance(Point lhs, Point rhs) { // 两点间距离
return module(rhs - lhs);
}
constexpr i64 Distance(Point rhs) const {
return Module(rhs - *this);
}
constexpr double distance(Point rhs) const {
return module(rhs - *this);
}
constexpr static i64 Distance(Point lhs, int nx, int ny) {
return Module(Point(nx, ny) - lhs);
}
constexpr static double distance(Point lhs, int nx, int ny) {
return module(Point(nx, ny) - lhs);
}
constexpr i64 Distance(int nx, int ny) const {
return Distance(*this, nx, ny);
}
constexpr double distance(int nx, int ny) const {
return distance(*this, nx, ny);
}
constexpr i64 cross(Point o, Point lhs, Point rhs) const {
lhs -= o;
rhs -= o;
return lhs ^ rhs;
}
constexpr i64 cross(Point lhs, Point rhs) const {
return cross(*this, lhs, rhs);
}
constexpr i64 Area(Point o, Point lhs, Point rhs) const { // 平行四边形的面积
return std::abs(cross(o, lhs, rhs));
}
constexpr i64 Area(Point lhs, Point rhs) const {
return Area(*this, lhs, rhs);
}
constexpr bool onSegment(Point c, Point lhs, Point rhs) const {
lhs -= c;
rhs -= c;
return (lhs ^ rhs) == 0 and (lhs * rhs) <= 0;
}
constexpr bool onSegment(Point lhs, Point rhs) const {
return onSegment(*this, lhs, rhs);
}
friend constexpr bool operator<(Point lhs, Point rhs) {
int al = area(lhs);
int ar = area(rhs);
if (al != ar) {
return al < ar;
}
i64 t = lhs ^ rhs;
if (t != 0) {
return t > 0;
}
return Module(lhs) < Module(rhs);
}
friend constexpr bool operator==(Point lhs, Point rhs) {
return lhs.x == rhs.x and lhs.y == rhs.y;
}
friend constexpr bool operator!=(Point lhs, Point rhs) {
return lhs.x != rhs.x or lhs.y != rhs.y;
}
friend constexpr std::istream &operator>>(std::istream &is, Point &a) {
return is >> a.x >> a.y;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const Point &a) {
return os << a.x << " " << a.y;
}
};
std::vector<Point> getConvexHull(std::vector<Point> &pt) {
std::sort(pt.begin(), pt.end(), [&](const auto &a, const auto &b) {
return a.y != b.y ? a.y < b.y: a.x < b.x;
});
std::vector<Point> lo;
for (const auto &p: pt) {
while (lo.size() > 1 and lo[lo.size() - 2].cross(lo.back(), p) <= 0) {
lo.pop_back();
}
lo.push_back(p);
}
lo.pop_back();
std::vector<Point> hi;
for (const auto &p: pt | std::views::reverse) {
while (hi.size() > 1 and hi[hi.size() - 2].cross(hi.back(), p) <= 0) {
hi.pop_back();
}
hi.push_back(p);
}
hi.pop_back();
lo.insert(lo.end(), hi.begin(), hi.end());
return lo;
}
i64 calcConvexHullArea(const std::vector<Point> &hull) {
i64 ans = hull.back() ^ hull[0];
for (int i = 0; i + 1 < hull.size(); i++) {
ans += hull[i] ^ hull[i + 1];
}
return std::abs(ans);
}
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
for (auto &x: v) {
is >> x;
}
return is;
}
struct Wave {
int m = 0;
int k = 0;
std::vector<Point> pt;
};
constexpr i64 inf = std::numeric_limits<i64>::max() / 4;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<Wave> wave(n);
for (auto &[m, k, pt]: wave) {
std::cin >> m >> k;
pt.resize(m);
std::cin >> pt;
}
const int N = 1 << n;
std::vector<i64> Area(N);
for (int msk = 1; msk < N; msk++) {
std::vector<Point> pt;
for (int i = 0; i < n; i++) {
if (msk >> i & 1) {
for (const auto &p: wave[i].pt) {
pt.push_back(p);
}
}
}
auto hull = getConvexHull(pt);
Area[msk] = calcConvexHullArea(hull);
}
std::vector<i64> dp(N, -inf);
dp[0] = 0;
for (int msk = 0; msk < N; msk++) {
for (int i = 0; i < n; i++) {
if (~msk >> i & 1) {
int nmsk = msk | (1 << i);
i64 add = Area[nmsk] - Area[msk];
dp[nmsk] = std::max(dp[nmsk], dp[msk] + wave[i].k * add);
}
}
}
i64 ans = 0;
for (int msk = 1; msk < N; msk++) {
ans = std::max(ans, dp[msk] + dp[N - 1 - msk]);
}
std::cout << ans << "\n";
return 0;
}
附上 P o i n t d o u b l e \rm{Point \ double} Point double 板子
constexpr double eps = 1E-9;
struct Point {
double x, y;
constexpr Point(): x{}, y{} {}
constexpr Point(double _x, double _y): x{_x}, y{_y} {}
constexpr Point(const Point &p): x{p.x}, y{p.y} {}
constexpr void reset(double nx = 0, double ny = 0) {
x = nx;
y = ny;
}
constexpr static double area(Point a) {
if (a.x >= eps and a.y > -eps) {
return 1;
} else if (a.x < eps and a.y >= eps) {
return 2;
} else if (a.x <= -eps and a.y < eps) {
return 3;
}
return 4;
}
constexpr static double angle(Point a) {
return atan2(a.y, a.x);
}
constexpr double angle() const {
return angle(*this);
}
constexpr Point rotate(int mode = 0) {
if (mode) {
std::tie(x, y) = std::pair {y, -x};
} else {
std::tie(x, y) = std::pair {-y, x};
}
return *this;
}
constexpr Point &operator+=(Point rhs) & {
x += rhs.x;
y += rhs.y;
return *this;
}
constexpr Point &operator-=(Point rhs) & {
x -= rhs.x;
y -= rhs.y;
return *this;
}
friend constexpr Point operator+(Point lhs, Point rhs) {
Point res = lhs;
res += rhs;
return res;
}
friend constexpr Point operator-(Point lhs, Point rhs) {
Point res = lhs;
res -= rhs;
return res;
}
constexpr Point operator*=(double k) & {
// assert(std::abs(k) >= eps);
x *= k;
y *= k;
return *this;
}
friend constexpr Point operator*(Point lhs, double k) {
Point res = lhs;
res *= k;
return res;
}
friend constexpr Point operator*(double k, Point rhs) {
Point res = rhs;
res *= k;
return res;
}
friend constexpr double operator*(Point lhs, Point rhs) { // 内积
double res = lhs.x * rhs.x + lhs.y * rhs.y;
return res;
}
friend constexpr double operator^(Point lhs, Point rhs) { // 叉乘
double res = lhs.x * rhs.y - lhs.y * rhs.x;
return res;
}
constexpr static double Module(Point a) { // 到原点距离的平方
return a * a;
}
constexpr static double module(Point a) { // 到原点的距离
return std::sqrt(Module(a));
}
constexpr double Module() const { // 自身模长的平方
return Module(*this);
}
constexpr double module() const { // 自身模长
return module(*this);
}
constexpr Point norm() {
double len = module(*this);
assert(len >= eps);
x /= len;
y /= len;
return *this;
}
constexpr double cos(Point lhs, Point rhs) const { // 两个向量的夹角余弦值
return (lhs * rhs) / module(lhs) / module(rhs);
}
constexpr double cos(Point rhs) const {
return cos(*this, rhs);
}
constexpr static double Distance(Point lhs, Point rhs) { // 两点间距离的平方
return Module(rhs - lhs);
}
constexpr static double distance(Point lhs, Point rhs) { // 两点间距离
return module(rhs - lhs);
}
constexpr double Distance(Point rhs) const {
return Module(rhs - *this);
}
constexpr double distance(Point rhs) const {
return module(rhs - *this);
}
constexpr static double Distance(Point lhs, double nx, double ny) {
return Module(Point(nx, ny) - lhs);
}
constexpr static double distance(Point lhs, double nx, double ny) {
return module(Point(nx, ny) - lhs);
}
constexpr double Distance(double nx, double ny) const {
return Distance(*this, nx, ny);
}
constexpr double distance(double nx, double ny) const {
return distance(*this, nx, ny);
}
constexpr double cross(Point o, Point lhs, Point rhs) const {
lhs -= o;
rhs -= o;
return lhs ^ rhs;
}
constexpr double cross(Point lhs, Point rhs) const {
return cross(*this, lhs, rhs);
}
constexpr double Area(Point o, Point lhs, Point rhs) const { // 平行四边形的面积
return std::abs(cross(o, lhs, rhs));
}
constexpr double Area(Point lhs, Point rhs) const {
return Area(*this, lhs, rhs);
}
constexpr bool onSegment(Point c, Point lhs, Point rhs) const {
lhs -= c;
rhs -= c;
return std::abs(lhs ^ rhs) < eps and (lhs * rhs) < eps;
}
constexpr bool onSegment(Point lhs, Point rhs) const {
return onSegment(*this, lhs, rhs);
}
friend constexpr bool operator<(Point lhs, Point rhs) {
int al = area(lhs);
int ar = area(rhs);
if (al != ar) {
return al < ar;
}
double t = lhs ^ rhs;
if (std::abs(t) >= eps) {
return t > 0;
}
return Module(lhs) < Module(rhs);
}
friend constexpr bool operator==(Point lhs, Point rhs) {
return std::abs(rhs.x - lhs.x) < eps and std::abs(rhs.y - lhs.y) < eps;
}
friend constexpr bool operator!=(Point lhs, Point rhs) {
return std::abs(rhs.x - lhs.x) >= eps or std::abs(rhs.y - lhs.y) >= eps;
}
friend constexpr std::istream &operator>>(std::istream &is, Point &a) {
return is >> a.x >> a.y;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const Point &a) {
return os << std::fixed << std::setprecision(5) << (std::abs(a.x) < eps ? 0: a.x) << " " << (std::abs(a.y) < eps ? 0: a.y);
}
};
struct Line {
Point a, v;
constexpr Line(): a{}, v{} {}
constexpr Line(const Line &l): a{l.a}, v{l.v} { v.norm(); }
constexpr Line(Point _a, Point _b): a{_a}, v{_b - _a} { v.norm(); }
constexpr Line(Point _a, double k): a{_a}, v{1, k} { v.norm(); }
constexpr Line(double k, double b): a{0, b}, v{1, k} { v.norm(); }
constexpr double getY(double x) const {
assert(std::abs(v.x) >= eps);
return v.y * (x - a.x) / v.x + a.y;
}
constexpr double getX(double y) const {
assert(std::abs(v.y) >= eps);
return v.x * (y - a.y) / v.y + a.x;
}
constexpr double getY(Line l, double x) const {
return l.getY(x);
}
constexpr double getX(Line l, double y) const {
return l.getX(y);
}
constexpr double distance(Point p) const {
return p.Area(a, a + v) / v.module();
}
constexpr double distance(Line l, Point p) const {
return l.distance(p);
}
constexpr Point pedal(Point p) const {
Point ap = p - a;
return a + ap.module() * ap.cos(v) * v;
}
constexpr Point pedal(Line l, Point p) const {
return l.pedal(p);
}
constexpr Line perpendicular(Point p) {
return Line {p, v.rotate()};
}
constexpr Line perpendicular(Line l, Point p) const {
return l.perpendicular(p);
}
constexpr Point intersect(Line m) const {
double denominator = v ^ m.v;
assert(std::abs(denominator) >= eps);
double t = ((m.a - a) ^ m.v) / denominator;
return a + t * v;
}
constexpr Point intersect(Line l, Line m) const {
return l.intersect(m);
}
constexpr bool contains(Point p) const {
return std::abs((a.y - p.y) * v.x - (a.x - p.x) * v.y) < eps;
}
constexpr bool contains(Line l, Point p) const {
return l.contains(p);
}
friend constexpr std::ostream &operator<<(std::ostream &os, const Line &l) {
os << "start point = " << l.a << "\nvector = " << l.v;
return os;
}
};
std::vector<Point> getConvexHull(std::vector<Point> &pt) {
std::sort(pt.begin(), pt.end(), [&](const auto &a, const auto &b) {
return std::abs(a.y - b.y) >= eps ? a.y <= b.y - eps: a.x <= b.x - eps;
});
std::vector<Point> lo;
for (const auto &p: pt) {
while (lo.size() > 1 and lo[lo.size() - 2].cross(lo.back(), p) < eps) {
lo.pop_back();
}
lo.push_back(p);
}
lo.pop_back();
std::vector<Point> hi;
for (const auto &p: pt | std::views::reverse) {
while (hi.size() > 1 and hi[hi.size() - 2].cross(hi.back(), p) < eps) {
hi.pop_back();
}
hi.push_back(p);
}
hi.pop_back();
lo.insert(lo.end(), hi.begin(), hi.end());
return lo;
}
double calcConvexHullArea(const std::vector<Point> &hull) {
double ans = hull.back() ^ hull[0];
for (int i = 0; i + 1 < hull.size(); i++) {
ans += hull[i] ^ hull[i + 1];
}
return std::abs(ans);
}
Line angleBisector(Point p, Point u, Point v) {
return Line {p, p + u + v};
}