AtCoder Beginner Contest 227(A-F)

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的矩阵, 每个矩阵有若干个豆子, 从左上角开始吃豆子,一次只能吃一个, 问有没有一种方案, 使得恰好吃完所有的豆子又回到左上角

这两个暂时不会

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值