AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AtCoder ABC227. Atcoder Beginner Contest 227 (C ~ G)    原题链接    困难

作者: 作者的头像   wkingyu ,  2024-10-02 01:34:59 ,  所有人可见 ,  阅读 31


2


$C.$ 给你一个整数 $N \le 10^{11}$,问有多少对 $A \le B \le C$ 满足 $ABC \le N$

思路:$AAA \le ABC \le N$,得到 $A \le N$,用 $N$ 的最大范围估计, $A \le 5000$,枚举 $A$ 的取值,那么 $BB \le BC \le \frac{N}{A}$,得到 $B \le \sqrt{\frac{N}{A}}$,枚举 $A = 1, 2, …, 5000$,估计一下 $\sum_{A=1}^{5000}\sqrt{\frac{N}{A}} = \sqrt{N} \times \sum_{A=1}^{5000}\sqrt{\frac{1}{A}} < 5000 \times (1 + \frac{1}{\sqrt{2}} + … + \frac{1}{\sqrt{5000}}) < 5000 \times 2 = 10^4$,时间复杂度来自枚举 $A$ 和 $B$,大概是 $5 \times 10^3 \times 10^4 = 5 \times 10^7$,可以接受

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int inf = 1 << 30;
const int mod = 1e9 + 7;
# define ALL(A) A.begin(),A.end()

void solve() {
    ll n;
    cin >> n;
    ll ans = 0;
    for (ll a = 1; a <= 5000LL; a ++)
        for (ll b = a; b * b <= n / a; b ++)
            ans += max(0LL, n / a / b - b + 1);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    T = 1;
    // cin >> T;
    while (T --)
        solve();
    return 0;
}

$D.$ 给你 $n$ 个公司,第 $i$ 个公司有 $a_i$ 个人,给定整数 $k$,你可以选择 $k$ 个不同公司,每个公司选一个人,完成一个项目,问最多能完成多少个项目

思路:转化为这样的问题:选择 $k$ 个组,组内可以有重复的元素,组间不可以重复,问组的最大容量是多少。使用二分,假设组的容量是 $x$,枚举每个公司的人数,如果 $a_i \ge x$,就从中取 $x$ 个人,构成一组,否则,记录所有 $a_i < x$ 的 $a_i$ 的总和 $s$,一旦 $s \ge x$,就取出 $x$ 个人构成一组,最后检查能否构成 $k$ 组即可

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int inf = 1 << 30;
const int mod = 1e9 + 7;
const ll INF = 2e18;
# define ALL(A) A.begin(),A.end()

void solve() {
    int n;
    ll k;
    cin >> n >> k;
    vector<ll> a(n, 0);
    for (int i = 0; i < n; i ++)
        cin >> a[i];
    ll l = 0, r = INF;
    while (l < r) {
        ll mid = l + r + 1 >> 1;
        // thred 总的轮数
        auto check = [&](ll thred) -> bool {
            ll tot = 0, s = 0;
            for (auto& x : a) {
                if (x >= thred) {
                    tot ++;
                    continue;
                }
                s += x;
                if (s >= thred) {
                    tot ++;
                    s -= thred;
                }
            }
            return tot >= k;
        };
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    cout << l << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    T = 1;
    // cin >> T;
    while (T --)
        solve();
    return 0;
}

$E.$ 给你一个字符串 $S$,由字母 $K, E, Y$ 组成,且 $|S| \le 30$,每次操作可以交换相邻的两个字符,给你一个整数 $K$,问至多进行 $K$ 次操作,能得到多少种不同的字符串

思路:字符串全排列的数量为 $\frac{3^{30}}{{cnt_{K} \cdot} cnt_E \cdot cnt_Y}$,其中 $cnt_x$ 代表字符 $x$ 出现的次数,次数都小于 $30$,全排列的复杂度 $> \frac{3^{30}}{30\cdot30\cdot30}$,显然是不可接受的。对于字符串 $S$,最多有交换 $C_{|S|}^2$ 种不同的交换方式,$k$ 的有效值最大是 $C_{|S|}^2$。考虑用 $DP$ 计数,用 $dp[i][j][k][t]$ 表示使用 $i$ 个 K,$j$ 个 E,$k$ 个 Y,交换次数为 $t$ 的方案个数,初始时 $dp[0][0][0][0] = 1$,考虑新增加一个字符时,状态怎么转移,不妨假设新增加的字符是 K,我们已经排好了前 $i - 1 + j + k$ 个字符,包括 $i - 1$ 个 K,$j$ 个 E,$k$ 个 Y,将把 K 放到第 $i + j + k$ 个位置,用一个位置数组,记录每个 K 在原字符串中的位置,可以从中获取要增加的第 $i$ 个 K 的位置,由于 K 之后的 E 和 Y 字符,如果放到 K 前面的话,会导致 K 往后移动,我们需要计算出,有多少个 E 和 Y 排到了 K 的前面,可以用二分和计数来解决,得到 K 移动后的新位置 $P_{now}$,增加字符 K 需要操作的次数为 $num = P_{now} - (i + j + k)$,得到转移方程

  • $dp[i][j][k][t] = dp[i][j][k][t] + dp[i - 1][j][k][t - num]$

同理可以增加 E 和 Y,并进行状态转移

复杂度 $O(n^3 \cdot n^2 \cdot log(n)) = O(n^5 \cdot log(n))$

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int inf = 1 << 30;
const int mod = 1e9 + 7;
const ll INF = 2e18;
# define ALL(A) A.begin(),A.end()

// dp[i][j][k][t] 表示排好了 i 个 "K",j 个 "E", k 个 "Y",交换 t 次的方案数
ll dp[35][35][35][910];

void solve() {
    string s;
    int K;
    cin >> s >> K;
    // 最多有 C_n^2 种交换方式
    int n = s.size();
    K = min(K, n * (n - 1) / 2);
    vector<vector<int>> pos(3);
    for (int i = 1; i <= n; i ++) {
        char c = s[i - 1];
        if (c == 'K')
            pos[0].push_back(i);
        else if (c == 'E')
            pos[1].push_back(i);
        else
            pos[2].push_back(i);
    }
    int a = pos[0].size();
    int b = pos[1].size();
    int c = pos[2].size();
    dp[0][0][0][0] = 1;
    // 30 * 30 * 30 * 900
    for (int i = 0; i <= a; i ++)
        for (int j = 0; j <= b; j ++)
            for (int k = 0; k <= c; k ++)
                for (int t = 0; t <= K; t ++) {
                    // 当前放 'K'
                    if (i >= 1) {
                        int p = pos[0][i - 1];
                        int j1 = lower_bound(ALL(pos[1]), p) - pos[1].begin();
                        int j2 = lower_bound(ALL(pos[2]), p) - pos[2].begin();
                        int nowP = p + max(0, j - j1) + max(0, k - j2);
                        int num = nowP - (i + j + k);
                        if (t >= num)
                            dp[i][j][k][t] += dp[i - 1][j][k][t - num];
                    }
                    // 当前放 'E'
                    if (j >= 1) {
                        int p = pos[1][j - 1];
                        int j0 = lower_bound(ALL(pos[0]), p) - pos[0].begin();
                        int j2 = lower_bound(ALL(pos[2]), p) - pos[2].begin();
                        int nowP = p + max(0, i - j0) + max(0, k - j2);
                        int num = nowP - (i + j + k);
                        if (t >= num)
                            dp[i][j][k][t] += dp[i][j - 1][k][t - num];
                    }
                    // 当前放 'Y'
                    if (k >= 1) {
                        int p = pos[2][k - 1];
                        int j0 = lower_bound(ALL(pos[0]), p) - pos[0].begin();
                        int j1 = lower_bound(ALL(pos[1]), p) - pos[1].begin();
                        int nowP = p + max(0, i - j0) + max(0, j - j1);
                        int num = nowP - (i + j + k);
                        if (t >= num)
                            dp[i][j][k][t] += dp[i][j][k - 1][t - num];
                    }
                }
    ll ans = 0;
    for (int t = 0; t <= K; t ++)
        ans += dp[a][b][c][t];
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    T = 1;
    // cin >> T;
    while (T --)
        solve();
    return 0;
}

$F.$ 给你一个 $n \times m$ 的矩阵,$n, m \le 30$,每个位置有一个数 $a_{i,j}$,要求你从 $(1, 1)$ 走到 $(n, m)$,每次只能向右或者向下走,给你一个整数 $K$,问所有路径中,前 $K$ 大值的和,最小是多少

思路:对于第 $K$ 大值问题,可以考虑枚举这个值是多少,假设我们枚举的第 $K$ 大值是 $x$,用 $dp[i][j][k]$ 表示走到 $(i, j)$,且选了 $k$ 个 $\ge x$ 的值时,$\ge x$ 的部分,和的最小值,假设 $a_{i,j} = y$,所有可能的情况如下:

  • 当 $y > x$ 时,这个数一定要加上,因为这个数会影响 $x$ 是不是第 $K$ 大的
  • 当 $y < x$ 时,这个数不需要加上,因为 $y$ 比第 $K$ 大值 $x$ 小,不会影响前 $K$ 大和
  • 当 $y = x$ 时,这个数可选可不选,因为当 $x$ 位于第 $K$ 大位置时,插入 $y$ 不会影响第 $K$ 大值是 $x$,可以把 $y$ 放到 $\ge x$ 的部分占位置,也可以把 $y$ 放到 $\le x$ 的部分,让它对结果不产生影响

综上,状态转移为:

  • 如果 $y \ge x$,$dp[i][j][k] = min(dp[i - 1][j][k - 1], dp[i][j - 1][k - 1]) + y$
  • 如果 $y \le x$,$dp[i][j][k] = min(dp[i - 1][j][k], dp[i][j - 1][k])$

时间复杂度为 $n^2 \times n^2 \times (n + m) = O(n^5)$,其中 $n = 30$,$30^5 \approx 2\times 10^7 $

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int inf = 1 << 30;
const int mod = 1e9 + 7;
const ll INF = 2e18;
# define ALL(A) A.begin(),A.end()

void solve() {
    int n, m, K;
    cin >> n >> m >> K;
    vector<vector<int>> a(n + 1, vector<int>(m + 1, 0));
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            cin >> a[i][j];
    ll ans = INF;
    // 枚举所选数的最小值是多少
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++) {
            // (1, 1) 和 (n, m) 是必选的
            int num = a[i][j];
            vector<vector<vector<ll>>> dp(n + 1, vector<vector<ll>>(m + 1, vector<ll>(K + 1, INF)));
            dp[0][1][0] = dp[1][0][0] = 0;
            for (int o1 = 1; o1 <= n; o1 ++)
                for (int o2 = 1; o2 <= m; o2 ++) {
                    // 可选
                    if (a[o1][o2] >= num) {
                        // 大于等于 num 的个数
                        for (int k = 1; k <= K; k ++) {
                            if (dp[o1 - 1][o2][k - 1] != INF)
                                dp[o1][o2][k] = min(dp[o1][o2][k], dp[o1 - 1][o2][k - 1] + a[o1][o2]);
                            if (dp[o1][o2 - 1][k - 1] != INF)
                                dp[o1][o2][k] = min(dp[o1][o2][k], dp[o1][o2 - 1][k - 1] + a[o1][o2]);
                        }
                    }
                    // 可不选
                    if (a[o1][o2] <= num) {
                        // 大于等于 num 的个数
                        for (int k = 0; k <= K; k ++) {
                            if (dp[o1 - 1][o2][k] != INF)
                                dp[o1][o2][k] = min(dp[o1][o2][k], dp[o1 - 1][o2][k]);
                            if (dp[o1][o2 - 1][k] != INF)
                                dp[o1][o2][k] = min(dp[o1][o2][k], dp[o1][o2 - 1][k]);
                        }
                    }
                }
            ans = min(ans, dp[n][m][K]);
        }
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    T = 1;
    // cin >> T;
    while (T --)
        solve();
    return 0;
}

$G.$ 给你两个数 $N \le 10^{12}$,$K \le min(10^6, N)$,求 $C_N^K$ 的约数个数(对 $998244353$ 取模)

思路:关键在于 $K$ 的范围,$K \le 10^6$,$C_N^K = \frac{\prod_{i=N-K+1}^{N}i}{\prod_{i=1}^Ki}$,可以看到分子和分母,区间的长度都是 $K \le 10^6$,根据算术基本定理和组合数学,一个数 $x = p_1^{k_1} p_2^{k_2} … p_m^{k_m}$ 的约数个数为 $\prod_{i=1}^{m}(k_i+1)$,只需关心 $C_N^K$ 分解质因数后,质因数的次方 $k_i$。使用埃式筛,可以用 $O(n \log(n) \log(n))$ 的复杂度,用 $1$ ~ $n$ 内质数去筛一个区间 $[L, R]$,把区间 $[L, R]$ 范围内的数存到下标在 $[1, R - L + 1]$ 的数组 $now$ 中,在埃式筛的第二个循环中,用 $n$ 范围内的质数去筛,对于 $C_N^K$ 的分母,直接取 $n=10^6$,可以把区间内所有的质因数筛出来,而对于分子,我们也取 $n=10^6$,可以筛出所有 $\le 10^6$ 的因子,筛完这部分后,还有一部分 $> 10^6$ 的质因子,对于这部分质因子,我们使用 $now$ 数组,对于所有 $now[i] \neq 1$ 的位置,表示对应的数是 $> 10^6$ 的质数,并且次方为 $1$,也统计到答案中即可。

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int inf = 1 << 30;
const int mod = 998244353;
const ll INF = 2e18;
const int N = 1000010;
# define ALL(A) A.begin(),A.end()

void solve() {
    ll n, k;
    cin >> n >> k;
    vector<ll> cnt(N, 0);
    vector<ll> now(k + 1, 0);
    for (int i = 1; i <= k; i ++)
        now[i] = i;
    for (int i = 2; i <= N; i ++) {
        for (int j = i; j <= k; j += i) {
            while (now[j] % i == 0) {
                now[j] /= i;
                cnt[i] --;
            }
        }
    }
    // 把数字 [n - k + 1, n] 放到下标 [1, k]
    for (int i = 1; i <= k; i ++)
        now[i] = n - k + i;
    for (int i = 2; i <= N; i ++) {
        // 找到大于等于 n - k + 1 的第一个 i
        ll start = (n - k + 1 + i - 1) / i * i;
        for (ll j = start; j <= n; j += i)
            while (now[j - (n - k)] % i == 0){
                now[j - (n - k)] /= i;
                cnt[i] ++;
            }
    }
    ll ans = 1;
    for (int i = 1; i <= N; i ++)
        ans = ans * (cnt[i] + 1) % mod;
    for (int i = 1; i <= k; i ++)
        if (now[i] != 1)
            ans = ans * 2 % mod;
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    T = 1;
    // cin >> T;
    while (T --)
        solve();
    return 0;
}

0 评论

App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息