AtCoder Beginner Contest 227(A-F)

本文详细解析了AtCoder Beginner Contest 227的题目,涵盖模拟、打表法、数学、二分、贪心、动态规划等多个算法知识点,并提供了典型题目的解题思路和代码实现,适合编程竞赛爱好者学习和复习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AtCoder Beginner Contest 227(A-F)

知识点整理:

题号知识点备注
A模拟,数学
B打表法
C数学
D二分,贪心
E字符串,DP建议顺便做下leetcode 777
FDP好题, 经典题的变形
G数论Poj2992, 数据范围加大
H图论,欧拉路,网络流

本次比赛大概比之前的ABC难一个字母, C相当于D,D相当于E这样子.

简单题


A - Last Card

N个人站成一圈, 从A号人开始依次发卡片,问第K张卡片在谁那里

看到数据范围只有1000, 直接模拟发卡片过程. 懒得推什么模值

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main() {
    int n, k, a;
    cin >> n >> k >> a;

    int start = a;
    for (int i = 1; i < k; i ++) {
        start += 1;
        if (start > n) start = 1;
    }
    cout << start << endl;
    return 0;
}

B - KEYENCE building

给你 KEYENCE大楼的面积公式 4 a b + 3 a + 3 b . 4ab+3a+3b. 4ab+3a+3b., 以及一些同学猜测的值, 问有多少人一定猜的是错的?

考虑到 a , b a,b a,b很小,直接打表把1000以内所有的算出来,装到set里面

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

set<int> s;
int n, a;

void init() {
    for (int a = 1; a <= 145; a++) {
        for (int b = 1; b <= 145; b++) {
            s.insert(4*a*b+3*a+3*b);
        }
    }
}
int main() {
    init();
    cin >> n;
    int res = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a;
        if (s.find(a) == s.end()) res++;
    }
    cout << res << endl;
    return 0;
}

C - ABC conjecture

问有多少个不下降的三元组满足 A B C ≤ N ABC \leq N ABCN?

枚举 A A A从1到 N 3 \sqrt[3]{N} 3N , 再枚举 B B B, 统计 C C C有多少种情况

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

ll n;

int main() {
    cin >> n;
    ll res = 0;
    for (ll a = 1; a*a*a <= n; a++) 
        for (ll b = a; a*b*b <= n; b++)
            res += n/a/b-b+1;
    
    cout << res << endl;
    return 0;
}

中等题


D - Project Planning

N N N个部门,每个部门有 A i A_i Ai个人, 现在需要举办活动, 每个活动抽 K K K个部门, 每个部门抽一个人, 问最多能举办多少个这样的活动?

这道题我一开始想的是排序后二分, 但是发现写不出来, 看了官方题解茅塞顿开

二分答案 P P P, 可以推出一个结论: 令 s u m sum sum 表示数组中 m i n ( A i , P ) min(A_i, P) min(Ai,P)的和, 如果 P ∗ K > s u m P * K > sum PK>sum, 则一定可以组织 P P P个活动, 否则不能

可以这样理解, 每次从数组中选择 K K K个数减1, 减 P P P次, 则数组任何一个数最多不能被减超过 P P P次, 所以只要 s u m sum sum P ∗ K P * K PK.大, 就确保了这 P P P 次操作都有的减. 方案是每次取数组中前 K K K大的数, 只要都比 1 1 1大即可

每次计算 s u m sum sum的复杂度是 O ( n ) O(n) O(n), 二分范围不超过64次, 时间复杂度约为 O ( n l o g M a x ( a i ) ) O(nlogMax(a_i)) O(nlogMax(ai)) 肯定可过

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 2e5 + 10;
int n, k;
ll a[N];

bool check(ll p) {
    ll sum = 0;
    for (int i = 1; i <= n; i++) sum += min(a[i], p);

    return p*k > sum;
}

int main() {
    cin >> n >> k;

    for (int i = 1; i <= n; i++) cin >> a[i];

    ll l = 0, r = 1e18 / k; // 避免溢出

    while (l < r) {
        ll mid = (l+r+1ll) >> 1;
        // P*K > sum
        if (check(mid)) r = mid-1;
        else l = mid;
    }
    cout << l << endl;
    return 0;
}

E - Swap

题意:

给你一个字符串由K, E, Y 组成, 允许交换相邻的字符 K K K次, 问能形成多少种不同的字符串?

题解

我跟答案一样, 考虑的问题是:

给你两个字符串 A A A, B B B, 问 A A A 通过几次相邻字符的交换能变成 B B B?

这个问题临场死活没想明白, 看leetcode有一个类似题, 但是套不上去, 随即放弃

看答案, 这个问题真的让我五体投地, 可以用dp来做:

d p [ i ] [ x ] [ e ] [ y ] dp[i][x][e][y] dp[i][x][e][y] 表示 B B B字符串的前 i i i位, 有 e e e个E, y y y个Y, 经过 x x x次操作后, 能与 A A A串的前 i − e − y i-e-y iey个K, e e e个E, y y y个Y相对顺序一致的方案数

有点像这样

A: KKKKKK YYYYYYY E ........

B: KYE.................................

image-20211115071258321

假设有一个B串是KYE, 那么B串的KYE和A串第一个k 第一个y 第一个E连线 要求是没有交叉的

这个转移是逆向的, 我们看当前状态 d p [ i ] [ x ] [ e ] [ y ] dp[i][x][e][y] dp[i][x][e][y] 这么多种字符串对哪些 d p [ i + 1 ] dp[i+1] dp[i+1]状态有贡献:

  • 在这种状态下的所有串后面加个K: e,y两个维度不变, 看x变化了多少. 我们需要把这个字符K往左移, 移到哪里呢? 移到上面图中不出现交叉线即可, 那么我们看A串中第i-e-y+1个K在哪, 然后看有多少个e和y和这条线交叉了, 就需要把这个K往左挪几格, 记为cnt, 那么 d p [ i + 1 ] [ x + c n t ] [ e ] [ y ] + = d p [ i ] [ x ] [ e ] [ y ] dp[i+1][x+cnt][e][y] += dp[i][x][e][y] dp[i+1][x+cnt][e][y]+=dp[i][x][e][y]
  • 另外两个字母同理, 分别贡献到 d p [ i + 1 ] [ e + 1 ] [ y ] [ c + c n t ] , d p [ i + 1 ] [ e ] [ y + 1 ] [ c + c n t ] dp[i + 1][e + 1][y][c + cnt] , dp[i + 1][e][y + 1][c + cnt] dp[i+1][e+1][y][c+cnt],dp[i+1][e][y+1][c+cnt]

最终目标是B串的前n位经过小于等于k次操作后形成一堆竖条, 没有交点, 那么答案就是 ∑ j = 0 k d p [ n ] [ j ] [ e ] [ y ] \sum _{j=0}^kdp[n][j][e][y] j=0kdp[n][j][e][y], 看了下大佬的代码, 总的时间复杂度为 O ( n 5 ) O(n^5) O(n5)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 32;
const int CN2 = N * (N - 1) / 2;

string s;
int K;

ll dp[N][N][N][CN2];

int main() {
    cin >> s >> K;

    int n = s.size();

    K = min(K, n * (n - 1) / 2);
    vector<int> kPos, ePos, yPos;

    for (int i = 0; i < n; i++) {
        char ch = s[i];
        if (ch == 'K') kPos.push_back(i);
        if (ch == 'E') ePos.push_back(i);
        if (ch == 'Y') yPos.push_back(i);
    }

    int kCnt = kPos.size(), eCnt = ePos.size(), yCnt = yPos.size();

    dp[0][0][0][0] = 1;

    for (int i = 0; i < n; i++) {
        for (int e = 0; e <= eCnt; e++) {
            for (int y = 0; y <= yCnt; y++) {
                int kk = i - e - y;
                if (kk < 0 || kk > kCnt) continue;

                for (int sw = 0; sw <= K; sw++) {
                    // 这个状态对后面没贡献
                    if (!dp[i][e][y][sw]) continue;

                    // 试着省略?
                    if (kk < kCnt) {
                        int moveSteps = 0; // 把i+1位置的K往左交换
                        for (int l = 0; l < e; l++) if (ePos[l] > kPos[kk]) moveSteps++;
                        for (int l = 0; l < y; l++) if (yPos[l] > kPos[kk]) moveSteps++;

                        if (sw + moveSteps <= K) dp[i + 1][e][y][sw + moveSteps] += dp[i][e][y][sw];
                    }

                    if (e < eCnt) {
                        int moveSteps = 0;
                        for (int l = 0; l < kk; l++) if (kPos[l] > ePos[e]) moveSteps++;
                        for (int l = 0; l < y; l++) if (yPos[l] > ePos[e]) moveSteps++;

                        if (sw + moveSteps <= K) dp[i + 1][e + 1][y][sw + moveSteps] += dp[i][e][y][sw];
                    }

                    if (y < yCnt) {
                        int moveSteps = 0;
                        for (int l = 0; l < kk; l++) if (kPos[l] > yPos[y]) moveSteps++;
                        for (int l = 0; l < e; l++) if (ePos[l] > yPos[y]) moveSteps++;

                        if (sw + moveSteps <= K) dp[i + 1][e][y + 1][sw + moveSteps] += dp[i][e][y][sw];
                    }
                }
            }
        }
    }

    ll res = 0;

    for (int i = 0; i <= K; i++) res+= dp[n][eCnt][yCnt][i];

    cout << res << endl;
    return 0;
}

F - Treasure Hunting

题意:

矩阵每个格子有个数字, 从左上角走到右下角的路径中, 前 K K K大的和的最小值是多少?

题解

魔改的经典问题, K K K 的数取值一共就只有 N 2 = 900 N^2 = 900 N2=900 种情况, 我们逐一枚举, 分别判断有没有解, 以及 K K K的数为a[i][j]时, 前 K K K大数的最小和 是多少, 这个就比较好统计了, 不到 a [ i ] [ j ] a[i][j] a[i][j]的通通不算, 看走到头的时候比他大的数有多少个 总的时间复杂度为 O ( n 4 ) O(n^4) O(n4)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 32;
const int CN2 = N * (N - 1) / 2;

ll a[N][N];
int H, W, K;

ll dp[N][N];

ll solve(ll X) {
    memset(dp, 0x3f, sizeof(dp));

    dp[0][1] = dp[1][0] = 0;

    ll cnt = 0;

    for (int i = 1; i <= H; i++) {
        for (int j = 1; j <= W; j++) {
            dp[i][j] = min(dp[i-1][j], dp[i][j-1]);
            if (a[i][j] >= X) {
                dp[i][j] += a[i][j] - X;
                cnt ++;
            }
        }
    }

    if (cnt < K) return INT64_MAX; // 比X大的数都不到k个,无解

    return dp[H][W] + 1ll*K*X;
}
int main() {
    cin >> H >> W >> K;
    for (int i = 1; i <= H; i++) for (int j = 1; j <= W; j++) cin >> a[i][j];

    ll res = INT64_MAX;
    for (int i = 1; i <= H; i++)
        for (int j = 1; j <= W; j++)
            res = min(res, solve(a[i][j]));

    cout << res << endl;
    return 0;
}

高级题

G - Divisors of Binomial Coefficient

题意:

C n k C_n^k Cnk 有多少个因子

H - Eat Them All

题意:

给你一个3*3的矩阵, 每个矩阵有若干个豆子, 从左上角开始吃豆子,一次只能吃一个, 问有没有一种方案, 使得恰好吃完所有的豆子又回到左上角

这两个暂时不会

### AtCoder Beginner Contest 386 概述 AtCoder Beginner Contest (ABC) 是一项面向编程爱好者的在线竞赛活动,旨在通过解决算法问题来提升参赛者的能力。比赛通常每两周举行一次,每次持续100分钟。参与者需在规定时间内完成尽可能多的任务以获得更高排名。 对于 ABC 386 的具体详情如下: #### 比赛时间安排 赛事于日本标准时间(JST) 开始并结束,在此期间选手可以提交自己的解决方案给系统评测。需要注意的是,由于时区差异,请提前确认好本地的确切开赛时刻[^1]。 #### 参加方式与资格条件 任何拥有 AtCoder 账号的人都能报名参加该系列的比赛;不过为了确保公平竞争环境,官方建议新手先尝试较低编号的场次积累经验后再挑战更难的内容。此外,注册账户时所填写的信息务必真实有效以便后续处理成绩等相关事宜[^2]。 #### 题目概览 本次比赛中包含了多个不同难度级别的题目供各位挑战自我: - **A - Sample Problem**: 这是一道简单的入门级练习题,主要用来测试输入输出以及基本语法掌握情况。 - **B - Another Easy One**: 同样属于基础范畴内的另一类常见考题形式,考察逻辑思维能力和简单数据结构的应用技巧。 - **C - Bitwise Operations Practice**: 关注位运算操作及其应用场景下的快速计算方法实现。 - **D - Graph Theory Basics**: 接触图论基础知识的学习材料之一,涉及节点连接关系分析等内容。 - **E - Advanced Dynamic Programming Task**: 动态规划高级应用实例展示,适合有一定功底的同学深入研究学习。 - **F - Challenging Mathematics Puzzle**: 数学难题求解环节,考验综合运用各种数学工具解决问题的实际能力[^3]. #### 示例代码片段解析 下面给出一段用于处理特定查询请求的数据预处理部分伪代码作为参考: ```cpp // 假设 nx[][] 存储跳跃表信息, inf 表示无穷大值 for (int j = 1; j < 20; j++) for (int i = 1; i <= 2 * n; i++) nx[j][i] = nx[j - 1][i] == inf ? inf : nx[j - 1][nx[j - 1][i]]; ``` 这段代码实现了稀疏表格构建过程中的倍增优化策略,能够显著提高某些区间最远可达位置查找效率。其中 `j` 控制着当前考虑的最大步数幂次方级别,而内层循环则遍历所有可能起点更新其对应终点坐标[^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值