T O P

[资源分享]     2023牛客寒假算法基础集训营3 A-I+K

  • By - 楼主

  • 2023-01-25 00:08:46
  • A

    题解

    知识点:贪心。

    把所有正偶数除成奇数,即可。

    (人傻了没加 \(x>0\) WA2

    时间复杂度 \(O(n)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int n;
        cin >> n;
        ll ans = 0;
        for (int i = 1;i <= n;i++) {
            int x;
            cin >> x;
            while (x > 0 && x % 2 == 0) x /= 2;
            ans += x;
        }
        cout << ans << '\n';
        return 0;
    }
    

    B

    题解

    知识点:数学,构造。

    特判 \(n=2\) 无解。

    可以先放边长为 \(\left\lceil \dfrac{n}{2} \right\rceil\) 正方形,随后边长每增加 \(1\) 需要最少 \(3\) 块,直到边长为 \(2 \cdot \left\lceil \dfrac{n}{2} \right\rceil\) 后,边长每增加 \(1\) 需要最少 \(5\) 块。以此类推,当边长为 \(\left[(k-1)\cdot\left\lceil \dfrac{n}{2} \right\rceil,k\cdot\left\lceil \dfrac{n}{2} \right\rceil \right),k \in \N^+\) 时,边长每增加 \(1\) 需要 \(2k-1\) 块积木。

    显然,摆完第一轮边长为 \(\left\lceil \dfrac{n}{2} \right\rceil\) 后,剩下的 \(n - \left\lceil \dfrac{n}{2} \right\rceil\) 个积木,而 \(\left\lfloor \dfrac{n - \left\lceil \dfrac{n}{2} \right\rceil}{3} \right\rfloor \leq \left\lceil \dfrac{n}{2} \right\rceil\) ,因此不可能摆到需要 \(5\) 个积木的情况。

    综上,边长最大值为 \(\left\lceil \dfrac{n}{2} \right\rceil + \left\lfloor \dfrac{n - \left\lceil \dfrac{n}{2} \right\rceil}{3} \right\rfloor\)

    本题也可以用二分边长做。

    (没考虑 \(n - \left\lceil \dfrac{n}{2} \right\rceil\) 大小,傻了吧唧的算了通式,不过可以出题了qwq

    考虑 \(n\) 块积木,给定 \(m\) ,每块积木大小为 \(1 \times k,k \in \left[ 1,\left\lceil \dfrac{m}{2} \right\rceil \right]\) ,求能摆成正方形的边长最大值。

    时间复杂度 \(O(1)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    bool solve() {
        ll n;
        cin >> n;
        if (n == 2) return false;
        ll a = (n + 1) / 2;
        ll ans = a + (n - a) / 3;
        cout << ans << '\n';
        return true;
    }
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int t = 1;
        cin >> t;
        while (t--) {
            if (!solve()) cout << -1 << '\n';
        }
        return 0;
    }
    

    C

    题解

    知识点:构造。

    \(n \leq 3\)\(n = 7\) 时无解。

    考虑 \(n\bmod 4 = 0,1,2,3\) 的情况。

    1. \(n \bmod 4 = 0\) 时显然形如构造 \(3,4,1,2\) 的循环即可。
    2. \(n \bmod 4 = 1\) 时,前 \(5\) 项构造成 \(4,5,1,2,3\) ,其余仿照 \(n \bmod 4 = 0\) 情况。
    3. \(n \bmod 4 = 2\) 时,前 \(6\) 项构造成 \(4,5,6,1,2,3\) ,其余仿照 \(n \bmod 4 = 0\) 情况。
    4. \(n \bmod 4 = 3\) 时,前 \(11\) 项构造分为 \(5\) 项和 \(6\) 项两组仿照 \(n \bmod 4 = 1,2\) 情况,其余仿照 \(n \bmod 4 = 0\) 情况。

    时间复杂度 \(O(n)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    bool solve() {
        int n;
        cin >> n;
        if (n <= 3 || n == 7) return false;
        int m = 1;
        if (n % 4 == 1) {
            cout << "4 5 1 2 3" << ' ';
            m = 6;
        }
        else if (n % 4 == 2) {
            cout << "4 5 6 1 2 3" << ' ';
            m = 7;
        }
        else if (n % 4 == 3) {
            cout << "4 5 1 2 3 9 10 11 6 7 8" << ' ';
            m = 12;
        }
        for (int i = m;i <= n;i += 4) {
            cout << i + 2 << ' ' << i + 3 << ' ' << i << ' ' << i + 1 << ' ';
        }
        cout << '\n';
        return true;
    }
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int t = 1;
        //cin >> t;
        while (t--) {
            if (!solve()) cout << -1 << '\n';
        }
        return 0;
    }
    

    D

    题解

    知识点:博弈论。

    这类题需要先对局面分类,每种局面考虑找到一组平衡的操作,即对于其中一人,无论另一人如何操作,他都可以在下一次操作后回到原来的局面。

    考虑将 \(n\) 分奇偶情况:

    1. \(n\) 为偶数,小红每次可以选 \(1\) ,随后数变为奇数局面,小紫只有奇数因子能选,数又变为偶数局面。到最后,必然是小紫让数变为 \(0\) ,因为只有小紫能让数变为偶数。因此,偶数局面小红必胜。
    2. \(n\) 为奇数,根据 \(n\) 为偶数的推理,发现奇数局面小红必败。

    时间复杂度 \(O(1)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        ll n;
        cin >> n;
        if (n & 1) cout << "yukari" << '\n';
        else cout << "kou" << '\n';
        return 0;
    }
    

    E

    题解

    知识点:计算几何。

    \(A(x_A,y_A),B(x_B,y_B),C(x_C,y_C)\) 构成等腰直角三角形,其中 \(C\) 为顶点且在 \(AB\) 右侧,满足方程:

    \[\left\{ \begin{aligned} x_C+y_C = x_A + y_B\\ x_C-y_C = x_B - y_A \end{aligned} \right. \]

    方程可以通过全等三角形证明。

    显然 \(C\)\(C\) 关于 \(AB\) 的对称点同时是或不是整数点,解出 \(C(x_C,y_C)\) 后判断是否为整数即可。

    (平面几何永远的痛,并且以为无解输出-1收获WA

    时间复杂度 \(O(1)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        ll a, b, c, d;
        cin >> a >> b >> c >> d;
        ll x = a + d + c - b;
        ll y = a + d + b - c;
        if (x & 1 || y & 1)cout << "No Answer!" << '\n';
        else cout << x / 2 << ' ' << y / 2 << '\n';
    
        return 0;
    }
    

    F

    题解

    知识点:宇宙的终极答案。

    通过你高超的中文流读取技术,发现这是营销号特有的文案。

    本打算对此嗤之以鼻的你,阅读完样例后逐渐理解了一切,确信 \(42\) 就是宇宙的终极答案。

    时间复杂度 \(O(\infin)\)

    空间复杂度 \(O(\infin)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        cout << 42 << '\n';
        return 0;
    }
    

    G

    题解

    知识点:模拟,枚举,dfs。

    很简单(痛苦)的模拟。

    dfs枚举每个 ? 的三种可能即可,注意快速幂前把底数模一下,因为可能炸 long long

    可以选择预处理数字后边枚举边求值,也可以考虑枚举完再求值。注意,边枚举边求值不太适用于有优先级表达式。

    (被表达式求值整了一顿,码力太差了QAQ

    时间复杂度 \(O(3^{12} \cdot n)\)

    空间复杂度 \(O(n)\)

    代码

    边枚举边求值

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    
    ll qpow(ll a, ll k, ll P) {
        ll ans = 1;
        while (k) {
            if (k & 1) ans = ans * a % P;
            k >>= 1;
            a = a * a % P;
        }
        return ans;
    }
    
    int ans;
    vector<int> num;
    vector<char> op(20);
    bool dfs(int step = 1, ll cur = num[0]) {
        if (step == num.size()) return cur == ans;
        op[step] = '+';
        if (dfs(step + 1, cur + num[step])) return true;
        op[step] = '-';
        if (dfs(step + 1, cur - num[step])) return true;
        if (cur > 0 && num[step] > 0) {
            op[step] = '#';
            if (dfs(step + 1, qpow(cur % num[step], cur, num[step]))) return true;
        }
        return false;
    }
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        string s;
        cin >> s;
        for (int i = 0;i < s.size();i++) {
            if (isdigit(s[i])) ans = ans * 10 + s[i] - '0';
            else num.push_back(ans), ans = 0;
        }
        if (dfs()) {
            cout << num[0];
            for (int i = 1;i < num.size();i++) cout << op[i] << num[i];
            cout << '=' << ans << '\n';
        }
        else cout << -1 << '\n';
        return 0;
    }
    

    枚举完求值,用到表达式计算。

    这里给了一个模板,可以修改map的优先级,支持带括号的二元运算,以及伪负号运算(指负号运算必须打括号)。

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    ll qpow(ll a, ll k, ll P) {
        ll ans = 1;
        while (k) {
            if (k & 1) ans = ans * a % P;
            k >>= 1;
            a = a * a % P;
        }
        return ans;
    }
    
    map<char, int> mp = { {'+',0},{'-',0},{'#',0},{'=',0} };
    bool calc(string s) {
        vector<ll> num = { 0 };
        vector<char> op;
        for (auto ch : s) {
            if (ch >= '0' && ch <= '9') num.back() = num.back() * 10 + ch - '0';
            else {
                while (op.size() && mp[ch] <= mp[op.back()]) {
                    char ope = op.back();
                    op.pop_back();
                    ll x = num.back();
                    num.pop_back();
                    if (ope == '+') num.back() += x;
                    else if (ope == '-') num.back() -= x;
                    else if (ope == '#') {
                        if (x <= 0) return false;
                        num.back() = qpow(num.back() % x, num.back(), x);
                    }
                }
                if (ch == '#' && num.back() <= 0) return false;
                op.push_back(ch);
                num.push_back(0);
            }
        }
        return num[0] == num[1];
    }
    
    string s;
    bool dfs(int step = 0) {
        if (step == s.size()) return calc(s);
        if (s[step] == '?') {
            s[step] = '+';
            if (dfs(step + 1)) return true;
            s[step] = '-';
            if (dfs(step + 1)) return true;
            s[step] = '#';
            if (dfs(step + 1)) return true;
            s[step] = '?';
        }
        else {
            while (step < s.size() && s[step] != '?') step++;
            if (dfs(step)) return true;
        }
        return false;
    }
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        cin >> s;
        if (dfs()) cout << s << '\n';
        else cout << -1 << '\n';
        return 0;
    }
    

    H

    题解

    知识点:概率dp。

    可能重要的前置知识(大佬请跳过)

    对于一维期望dp,在第 \(i\) 步时,其向其他状态转移的起点只有 \(f_i\) 一种,因此在第 \(i\) 步起点是状态 \(f_i\) 的概率是百分百的,变化的期望直接加就行。

    例如,有期望状态 \(f_i\) 。操作有两种,概率分别为 \(\dfrac{1}{4},\dfrac{3}{4}\),两种操作的贡献分别是 \(1,3\) 。那么可以有转移方程:

    \[f_{i+1} = \dfrac{1}{4}(f_i+1) + \dfrac{3}{4}(f_i+3) \]

    但是,如果在一维期望dp的基础上,设每一步都有多个不同的状态,那么转移时的期望就不是简单加法了。

    具体的说,在第 \(i\) 步时,其向其他状态转移的起点如果是 \(f_{i,j}\)\(j\) 种,那么显然这 \(j\) 种状态都有概率成为起点,满足概率的总和为百分百。因此考虑 \(f_{i,j}\) 为起点做转移时,变化的期望需要乘上其作为起点的概率,表示这步操作在 \(f_{i,j}\) 作为起点的概率下的期望。当然,期望 \(f_{i,j}\) 本身不需要再乘一遍概率,因为求这个期望时已经考虑了到这个状态的概率,同时我们还可以知道这 \(j\) 种期望的总和就是第 \(i\) 步的总期望。

    例如,有期望状态 \(f_{i,0/1/2}\) ,设 \(f_{i,j}\) 的概率为 \(g_{i,j}\) 。操作有两种,概率分别为 \(\dfrac{1}{4},\dfrac{3}{4}\) ,我们假设从 \(f_{i,0/2}\) 都可以通过两种操作转移到 \(f_{i+1,0}\) ,两种操作的贡献对于两种状态分别是 \(1,2\)\(5,6\) 。那么对于 \(f_{i+1,0}\) 可以有转移方程:

    \[\begin{aligned} f_{i+1,0} &= \dfrac{1}{4}(f_{i,0} + 1\cdot g_{i,0}) + \dfrac{3}{4}(f_{i,0} + 2 \cdot g_{i,0})\\ &+\dfrac{1}{4}(f_{i,2} + 5\cdot g_{i,2})+\dfrac{3}{4}(f_{i,2} + 6 \cdot g_{i,2}) \end{aligned} \]

    接下来就可以轻松(真的吗)做这道题了。

    \(f_{i,j,k}\) 为执行到第 \(i\) 步且满足串首状态为 \(j\) 、串尾状态为 \(k\) 的期望个数。其中,\(j = 0/1\) 表示串首是 rededr\(k = 0/1\) 同理。

    \(g_{i,j,k}\) 为对应的 \(f_{i,j,k}\) 发生的概率。注意,除了四种串首尾的状态,还有一种空串的状态,这里没有标记到数组里,但是每步还是得自己手动加上去的,我们记 \(prob\) 为空串的概率,空串的期望为 \(0\) 不需要考虑。

    因此有转移方程:

    \[\left\{ \begin{aligned} f_{i+1,0,0} &= \frac{1}{3} \cdot ((0 + 1 \cdot prob) + (f_{i,0,0} + 1 \cdot g_{i,0,0})+(f_{i,0,1}+1\cdot g_{i,0,1}))\\ &+ \frac{1}{3} \cdot 0\\ &+ \frac{1}{3} \cdot (10 \cdot f_{i,0,0})\\ f_{i+1,0,1} &= \frac{1}{3} \cdot 0\\ &+ \frac{1}{3} \cdot ((f_{i,0,0} + 0 \cdot g_{i,0,0})+(f_{i,0,1}+1 \cdot g_{i,0,1}))\\ &+ \frac{1}{3} \cdot (10\cdot f_{i,0,1})\\ f_{i+1,1,0} &=\frac{1}{3} \cdot ((f_{i,1,0} + 1 \cdot g_{i,1,0})+(f_{i,1,1}+1 \cdot g_{i,1,1}))\\ &+\frac{1}{3} \cdot 0\\ &+\frac{1}{3} \cdot (10 \cdot f_{i,1,0})\\ f_{i+1,1,1} &= \frac{1}{3} \cdot 0\\ &+ \frac{1}{3} \cdot ((0 + 0 \cdot prob) + (f_{i,1,0} + 0 \cdot g_{i,1,0})+(f_{i,1,1}+1 \cdot g_{i,1,1}))\\ &+ \frac{1}{3} \cdot (10 \cdot f_{i,1,1} + 9 \cdot g_{i,1,1})\\ g_{i+1,0,0} &= \frac{1}{3} \cdot (prob + g_{i,0,0} + g_{i,0,1}) + \frac{1}{3} \cdot 0 + \frac{1}{3} \cdot g_{i,0,0}\\\ g_{i+1,0,1} &= \frac{1}{3} \cdot 0 + \frac{1}{3} \cdot (g_{i,0,0} + g_{i,0,1}) + \frac{1}{3} \cdot g_{i,0,1}\\\ g_{i+1,1,0} &= \frac{1}{3} \cdot (g_{i,1,0} + g_{i,1,1}) + \frac{1}{3} \cdot 0 + \frac{1}{3} \cdot g_{i,1,0}\\\ g_{i+1,1,1} &= \frac{1}{3} \cdot 0 + \frac{1}{3} \cdot (prob + g_{i,1,0} + g_{i,1,1}) + \frac{1}{3} \cdot g_{i,1,1}\\\ prob' &= \frac{1}{3} \cdot prob \end{aligned} \right. \]

    写的很详细了,三个 \(\dfrac{1}{3}\) 对应三种操作,分别算一下概率和期望转移就行。特别注意,\(f_{i+1,1,1}\) 的操作三转移可以产生十倍加九的期望。

    代码用滚动数组压缩了一维, \(f_{j,k,0/1}\) 代表第 \(i\) 步的各种概率/期望, \(g_{j,k,0/1}\) 代表第 \(i+1\) 步的各种概率/期望。并且,代码转移时是用子状态刷表,而非如上述转移方程填表,因为写起来比较方便。填表也能写,本质都是一样的,很好理解。

    推荐使用 Modint 不然开 long long 也救不了打 % 打到手酸。

    时间复杂度 \(O(k)\)

    空间复杂度 \(O(1)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    const int P = 1e9 + 7;
    struct Modint {
        int val;
        Modint(int _val = 0):val(_val %P) { format(); }
        Modint(ll _val):val(_val %P) { format(); }
    
        //if val in [-P,2P)
        //maybe slower than global version
        Modint &format() {
            if (val < 0) val += P;
            if (val >= P) val -= P;
            return *this;
        }
        Modint inv()const { return qpow(*this, P - 2); }
    
        Modint &operator+=(const Modint &x) { val += x.val;return format(); }
        Modint &operator-=(const Modint &x) { val -= x.val;return format(); }
        Modint &operator*=(const Modint &x) { val = 1LL * val * x.val % P;return *this; }
        Modint &operator/=(const Modint &x) { return *this *= x.inv(); }
        friend Modint operator-(const Modint &x) { return { -x.val }; }
        friend Modint operator+(Modint a, const Modint &b) { return a += b; }
        friend Modint operator-(Modint a, const Modint &b) { return a -= b; }
        friend Modint operator*(Modint a, const Modint &b) { return a *= b; }
        friend Modint operator/(Modint a, const Modint &b) { return a /= b; }
    
        friend Modint qpow(Modint a, ll k) {
            Modint ans = 1;
            while (k) {
                if (k & 1) ans = ans * a;
                k >>= 1;
                a = a * a;
            }
            return ans;
        }
    
        friend istream &operator>>(istream &is, Modint &x) {
            ll _x;
            is >> _x;
            x = { _x };
            return is;
        }
        friend ostream &operator<<(ostream &os, const Modint &x) { return os << x.val; }
    };
    /*
    f[0/1][0/1][0]:概率
    f[0/1][0/1][1]:期望
    00  red-red
    01  red-edr
    10  edr-red
    11  edr-edr
    注意还有一种空串情况
    需要每次操作前手动加
    */
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int k;
        cin >> k;
        array<array<array<Modint, 2>, 2>, 2> f = {};
        Modint inv3 = Modint(3).inv();
        Modint prob = 1;
        for (int i = 1;i <= k;i++) {
            //prod指空串概率,空串期望始终为0不需要管
            array<array<array<Modint, 2>, 2>, 2> g = {};
            g[0][0][0] = inv3 * prob;
            g[1][1][0] = inv3 * prob;
            g[0][0][1] = inv3 * prob;
            //空串到g[1][1]的期望还是0,不用管
            for (auto i : { 0,1 }) {
                for (auto j : { 0,1 }) {
                    g[i][0][0] += inv3 * f[i][j][0];//操作1后的概率
                    g[i][1][0] += inv3 * f[i][j][0];//操作2后的概率
                    g[i][j][0] += inv3 * f[i][j][0];//操作3后的概率
    
                    g[i][0][1] += inv3 * f[i][j][1];//操作1后的期望
                    g[i][0][1] += inv3 * f[i][j][0];
                    g[i][1][1] += inv3 * f[i][j][1];//操作2后的期望
                    if (j == 1) g[i][1][1] += inv3 * f[i][j][0];//只有?1才能加
                    g[i][j][1] += 10 * inv3 * f[i][j][1];//操作3后的期望
                    if (i == 1 && j == 1) g[i][j][1] += 9 * inv3 * f[i][j][0];//只有11能加9个
                }
            }
            prob *= inv3;
            f = g;
        }
        Modint sum = 0;
        for (auto i : { 0,1 })for (auto j : { 0,1 }) sum += f[i][j][1];
        cout << sum << '\n';
        return 0;
    }
    

    I

    题解

    知识点:数论,构造。

    1. 偶数情况,若 \(x-1\) 是素数构造 \(n = (x-1)^2\) ,则 \(f(n) = 1+x-1=x\) ; 若 \(x-3\) 是素数构造 \(n = 2(x-3)\) ,则 \(f(n) = 1+2+x-3=x\)

    2. 奇数情况,因为一定存在 \(1\) 因子,我们考虑使其他因子的和凑出一个偶数 \(x-1\) 。考虑最简单的素数情况,因为哥德巴赫猜想,一个大于等于 \(4\) 的偶数可以被分解成两个素数之和,我们只要找到两个不同的素数 \(p,q\) 使得 \(p+q = x-1\) ,那么构造 \(n = pq\) ,则 \(f(n) = 1+p+q=x\)

      注意,哥德巴赫猜想所述是两个素数之和,不是两个不同的素数。经过测试 int 范围内,大于等于 \(8\) 的偶数都可以被分解为两个不同的素数之和,因此大于等于 \(9\) 的奇数我们无脑无解即可。

      我们需要特判 \(x = 1,3,7\) ,因为这些情况确实有解,但不能通过哥德巴赫猜想构造。

    时间复杂度 \(O(x)\)

    空间复杂度 \(O(x)\)

    代码

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    
    const int N = 1e6 + 7;
    bool vis[N];
    vector<int> prime;
    void get_prime(int n) {
        for (int i = 2;i <= n;i++) {
            if (!vis[i]) prime.push_back(i);
            for (int j = 0;j < prime.size() && i * prime[j] <= n;j++) {
                vis[i * prime[j]] = 1;
                if (!(i % prime[j])) break;
            }
        }
    }
    
    bool solve() {
        int x;
        cin >> x;
        if (x == 1) {
            cout << 2 << '\n';
            return true;
        }
        if (x == 3) {
            cout << 4 << '\n';
            return true;
        }
        if (x == 7) {
            cout << 8 << '\n';
            return true;
        }
        if (x & 1) {
            for (int i = 0;i < prime.size() && 2 * prime[i] < x - 1;i++) {
                if (!vis[x - 1 - prime[i]]) {
                    cout << 1LL * prime[i] * (x - 1 - prime[i]) << '\n';
                    return true;
                }
            }
        }
        else {
            if (!vis[x - 1]) cout << 1LL * (x - 1) * (x - 1) << '\n';
            else cout << 1LL * 2 * (x - 3) << '\n';
            return true;
        }
        return false;
    }
    
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int t = 1;
        cin >> t;
        get_prime(1e6);
        while (t--) {
            if (!solve()) cout << -1 << '\n';
        }
        return 0;
    }
    

    K

    题解

    知识点:贪心,数学。

    给你前 \(n\) 种素数,每个素数有 \(a_i\) 个。

    \(size = \sum_{i=1}^n a_i\) 。现在将这 \(size\) 个素数排成一个序列,设 \(f(i)\) 为序列中 \([1,i]\) 的数的乘积的因子的数量。现在求 \([1,size]\)\(f(i)\) 的和,即 \(\sum_{i=1}^{size} f(i)\) ,的最大值。

    显然,我们需要尽可能让前面的 \(f(i)\) 的越大越好。我们知道,乘积的因子数量等于各个素数个数加 \(1\) 的乘积,通过一些尝试很容易发现均摊素数的个数,比连续安排同一种素数得到的结果要大很多,因此我们每次安排还能安排的素数中出现次数最小的那个素数。

    我们设 \(cnt_i\) 表示出现至少 \(i\) 次的素数个数,我们发现 \(f(i)\) 的结果呈现 \(2,2^2,\cdots,2^{cnt_1},2^{cnt_1-1} \cdot 3,\cdots,2^{cnt_1-cnt_2} \cdot 3^{cnt_2},\cdots\) 。直接求和要加 \(size\) 次是不可行的,因此我们先用差分维护好 \(cnt_i\) ,随后用等比公式对每 \(cnt_i\) 个数直接求和。

    \(pre\)\(cnt_{i-1}\) 段的最后一个数字,那么 \(cnt_i\) 段的总和为 \(pre \cdot \dfrac{1-\frac{i+1}{i}^{cnt_i}}{1-\frac{i+1}{i}}\) ,最后一个数字 \(pre' = pre \cdot \dfrac{i+1}{i}^{cnt_i}\) ,于是就可以递推求和了。

    时间复杂度 \(O(2 \cdot 10^5 + n)\)

    空间复杂度 \(O(2 \cdot 10^5)\)

    代码

    #include <bits/stdc++.h>
    using ll = long long;
    using namespace std;
    
    const int P = 1e9 + 7;
    ll qpow(ll a, ll k) {
        ll ans = 1;
        while (k) {
            if (k & 1) ans = ans * a % P;
            k >>= 1;
            a = a * a % P;
        }
        return ans;
    }
    
    ll inv(ll a) { return qpow(a, P - 2); }
    
    int cnt[200007];
    int main() {
        std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        int n;
        cin >> n;
        for (int i = 1;i <= n;i++) {
            int x;
            cin >> x;
            cnt[1]++;
            cnt[x + 1]--;
        }
        for (int i = 1;i <= 2e5;i++) cnt[i] += cnt[i - 1];
        int pre = 1;
        int ans = 0;
        for (int i = 1;i <= 2e5;i++) {
            int f = 1LL * (i + 1) * inv(i) % P;
            int g = qpow(f, cnt[i]);
            ans = (ans + 1LL * pre * f % P * (1 - g + P) % P * inv(1 - f + P) % P) % P;
            pre = 1LL * pre * g % P;
        }
        cout << ans << '\n';
        return 0;
    }
    
    Image

    本帖子中包含资源

    您需要 登录 才可以下载,没有帐号?立即注册