2025【深圳大学-腾讯云程序设计竞赛(正式赛)】题解(不含 F, G)

比赛链接

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=1nj=ink=ijpk. 如果有多个满足的,输出 字典序最小 的。

数据范围

  • 1 ≤ ∑ n ≤ 1 0 6 . 1 \leq \sum n \leq 10^6. 1n106.

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 ai0

现在顺序做这 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:=aiy 后(必须保证 a i ≥ 0 a_i \geq 0 ai0),才能存在一个正偶数 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, 1n,m5105,
  • 0 ≤ a i ≤ 1 0 9 , 0 \leq a_i \leq 10^9, 0ai109,
  • 1 ≤ l ≤ r ≤ n , 1 \leq l \leq r \leq n, 1lrn,
  • 0 ≤ ∣ k ∣ ≤ 1 0 9 . 0 \leq |k| \leq 10^9. 0k109.

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=sik,令 s i = s i − k s_i = s_{i - k} si=sik
  • i + k ≤ n i + k \leq n i+kn 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, 1n104,
  • 1 ≤ k ≤ 1 0 9 , 1 \leq k \leq 10^9, 1k109,
  • 保证 s s s 只含小写字母。

Solution

k ≥ n k \geq n kn 时显然已经无法操作。

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(n26)

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. 1n,ai1000.

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α2pkαk, 那么对于 p i ≤ V p_i \leq V piV α 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=2152133,则 B ( x ) = 10100100 ⋯ 00000 B(x) = 10100100 \cdots 00000 B(x)=1010010000000(共 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=0n(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=0k(in11)(ki11), 而根据上面的推论, 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=01(in11)(ki11). 同时又考虑到 k − i > 11 k - i > 11 ki>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=012i=01(in11)(ki11). 接着我们将里面展开 ∑ 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=012(0n11)(k11)+k=012(1n11)(k111), 继续化简得到 ∑ 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=011(k11)+(n11)k=011(k11).

  • 这个式子的前半部分就是只考虑前 11 11 11 个质数的组合,也就是说只要 x x x 含有一个大质数( > V > V >V)就不参与计算;
  • 后半部分的意思就是,枚举后 n − 11 n - 11 n11 个大质数,将含有 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 n11 次。

当然,为了知道大质数 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, n1000
  • 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 bit,你可以对下面两种操作二选一;
    • 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, 1n106,
  • 1 ≤ p ≤ 1 0 18 , 1 \leq p \leq 10^{18}, 1p1018,
  • − 1 0 9 ≤ a i ≤ 1 0 9 , -10^9 \leq a_i \leq 10^9, 109ai109,
  • 1 ≤ b i ≤ 1 0 9 . 1 \leq b_i \leq 10^9. 1bi109.

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} Pt1Pt2Pti 的所有二维点形成的凸包或退化多边形,面积为 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=1l2ki(SiSi1) 特别的 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, 1n15,
  • 1 ≤ m i ≤ 500 , 1 \leq m_i \leq 500, 1mi500,
  • 1 ≤ k i ≤ 1 0 5 . 1 \leq k_i \leq 10^5. 1ki105.

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 倍)。

其次是 单调链算法

它介绍了如何寻找一个点集的凸包,分成两部分,下半凸包和上半凸包。找的逻辑都是一样的:

  • 就是行进的方向一定是逆时针严格向左,即叉乘要正负号保持一致。

当然这里它对点的排序是按照 纵坐标横坐标升序排列 的,而不是 极角排序

我代码中默认的小于号重载就是极角排序。

时间复杂度 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};
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值