AtCoder Beginner Contest 336 A~F

A.Long Loong(格式输出)

题意:

给出一个正整数 X X X,请你输出一个'L',然后 X X X'o',最后各输出一个'n''g'

分析:

按要求输出即可。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

void solve() {
    int n;
    cin >> n;
    cout << 'L';
    for (int i = 0; i < n; i++) cout << 'o';
    cout << "ng" << endl;
}

int main() {
    solve();
    return 0;
}

B.CTZ(二进制)

题意:

给出一个正整数 X X X,请你求出 X X X在二进制下末尾有多少个连续的0

分析:

循环除 2 2 2直到输入的 X X X变为奇数为止,循环的次数即为答案。

优化

直接使用函数计算答案,答案为 l o g 2 l o w b i t ( X ) log_2^{lowbit(X)} log2lowbit(X)

Tips:系统封装了库函数 l o g 2 ( ) log2() log2()可用于计算 2 2 2为底数的对数, l o w b i t lowbit lowbit则需自己实现

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

void solve() {
    int n;
    cin >> n;
    int ans = 0;
    while (n > 0 && n % 2 == 0) {
        ans++;
        n /= 2;
    }
    cout << ans << endl;
}

int main() {
    solve();
    return 0;
}

C.Even Digits(思维)

题意:

仅能使用0, 2, 4, 6, 8作为十进制上每一位数字的选择,问能组成的第 N N N小的数字是多少(从小到大)。

分析:

不难发现,选择完最高位的数字后,假设后面还有 m m m位十进制数字需要选择,此时的方案总数共 5 k 5^{k} 5k种。

那么,可以先找到最高位的数字是多少,即先计算出第一个满足 ( m + 1 ) = 5 k ≥ N (m + 1) = 5^{k} \ge N m+1=5kN m m m,然后将 m m m作为除数依次计算得到每一位的数字即可。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

void solve() {
    ll n;
    cin >> n;
    n--;
    ll m = 5;
    while (m <= n) m *= 5;
    m /= 5;
    while (m > 0) {
        cout << (n / m) * 2;
        n %= m;
        m /= 5;
    }
    cout << endl;
}

int main() {
    solve();
    return 0;
}

D.Pyramid(DP)

题意:

金字塔定义:一个高度为 k k k的金字塔序列共包含 ( 2 k − 1 ) (2k - 1) (2k1)个数字,且序列格式为: 1 , 2 , . . . , k − 1 , k , k − 1 , . . . , 2 , 1 1, 2, ..., k - 1, k, k - 1, ..., 2, 1 1,2,...,k1,k,k1,...,2,1

给出一个包含 n n n个数字的序列 A = ( A 1 , A 2 , . . . , A n ) A = (A_1, A_2, ..., A_n) A=(A1,A2,...,An),你可以进行若干次以下操作:

  • 选择一个序列中的数字,将该数字减少 1 1 1

  • 移除序列开头或末尾的数字

问,能获得的所有金字塔序列中,高度最高的金字塔的高度是多少?

分析:

将金字塔序列从中心分成两半,使用 p r e [ i ] , n x t [ i ] pre[i], nxt[i] pre[i],nxt[i]分别表示以第 i i i个数字作为金字塔中心,能组成的最长前半和后半序列的长度。

对于以第 i i i个数字作为金字塔中心,需要考虑两种情况:

  1. 前/后面序列构造的金字塔高度很小,那么当前中心点再高也只能通过减少高度到前/后面序列的最高高度加一。

  2. 前/后面序列构造的金字塔高度很大,那么只能通过删除前/后面部分数字,然后将接近中心的部分数字依次减少1,此时的金字塔高度即为当前数字。

依次计算得到 p r e , n x t pre, nxt pre,nxt数组,再枚举所有点为中心,此时选择第 i i i个数字为中心,能构造的最高金字塔高度即为 m i n ( p r e [ i ] , n x t [ i ] ) min(pre[i], nxt[i]) min(pre[i],nxt[i]),记录所有能构造的金字塔中的最高高度即为答案。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 2e5 + 5e2;

int n, a[N], pre[N], nxt[N];

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        pre[i] = min(pre[i - 1] + 1, a[i]);
    }
    for (int i = n; i >= 1; i--) {
        nxt[i] = min(nxt[i + 1] + 1, a[i]);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        ans = max(ans, min(pre[i], nxt[i]));
    }
    cout << ans << endl;
}

int main() {
    solve();
    return 0;
}

E.Digit Sum Divisible(思维,枚举,dp)

题意:

对于一个数字 x x x,我们定义它各数位上数字的和为 s s s,如 x = 2024 , s = 2 + 0 + 2 + 4 = 8 x=2024, s=2+0+2+4=8 x=2024,s=2+0+2+4=8。现在我们想知道 1 − N 1-N 1N之间有多少个数字 x x x满足 x x x s s s的倍数。

分析:

本题 N ≤ 1 0 14 N\le10^{14} N1014,即我们可以认为 s ≤ 14 × 9 = 126 s\le14\times9=126 s14×9=126,即:无论如何, s s s最多也不会超过 126 126 126,因此,我们可以对满足 1 ≤ s ≤ 126 1\le s\le126 1s126 x ≤ N x\le N xN的数进行枚举,枚举的过程是通过枚举各个数位上的数来实现的。为了便于获取每一位上的数,我们可以将 N N N看做一个字符串, N [ d ] N[d] N[d]代表从高往低第 d d d位的值。

对于每一个可能的目标 t a r g e t target target,我们设置 d p dp dp数组, d p [ d ] [ c u r ] [ r m d ] dp[d][cur][rmd] dp[d][cur][rmd]表示:现在我们对第 d d d位的数进行枚举,之前枚举结果的数位和为 c u r cur cur,除以 t a r g e t target target的余数为 r m d rmd rmd的方案数。那么很容易想到:假设第 d d d位上我们放置 t t t,那么 d p [ d + 1 ] [ c u r + t ] [ ( r m d ∗ 10 + t ) % t a r g e t ] dp[d+1][cur+t][(rmd*10+t)\%target] dp[d+1][cur+t][(rmd10+t)%target] d p [ d ] [ c u r ] [ r m d ] dp[d][cur][rmd] dp[d][cur][rmd]的值直接相关,需要进行更新。

但是,本题有一个限制:我们构建的数字不能超过 N N N,那么当我们构建到第 d d d位时,就要根据之前构建的情况进行分类讨论。若我们构建出的数字从第 0 0 0位到第 d − 1 d-1 d1位都和 N N N一模一样(称为 f l a g flag flag条件),那么我们根据枚举对象 t t t的大小,进行分类讨论:

大小关系代表含义
t < N [ d ] t < N[d] t<N[d]枚举对象没有超过 n n n在这一位上的内容,低位可以是任意数字(都不会超过 n n n
t = N [ d ] t = N[d] t=N[d]枚举对象到第 d d d位仍然保持一致,继续传递下去,向后讨论
t > N [ d ] t > N[d] t>N[d]枚举对象超过了 n n n在这一位上的内容,不可能,无需考虑这一情况

而若 f l a g flag flag条件不满足,则后面的位置上可以随意枚举任意的数。因此我们增添一个维度 f l a g flag flag,来进行更新。当 d d d N N N的长度一致,且 r m d = 0 rmd=0 rmd=0(没有余数,是满足要求的数字),且 c u r = t a r g e t cur=target cur=target(创建出的数字数位和,与目标数位和一致)时,代表出现了一种新的方案。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
ll dp[20][300][300][5];
string n;
int a[20];

ll dfs(int target, int d, int cur, int rmd, int flag) {
    // 如果d超过n的位数,或者是i超过枚举目标target,都说明无解 
    if (d > n.size() || cur > target)return 0;
    /*
    如果d已经对应了n位数(可能有前导0,这是没关系的),
    且余数为0(可以整除),且目前的和cur与目标和target一致,那说明有一种可行方案 
    */
    if (d == n.size() && rmd == 0 && cur == target)return dp[d][cur][rmd][flag] = 1;
    if (dp[d][cur][rmd][flag] != -1)return dp[d][cur][rmd][flag];
    ll res = 0;
    //在枚举第d位的时候并不是任何数字都可以的,
    //还要考虑之前枚举的数与 n的对应位置上的数a[]是否一致
    //若一致的话,这里就只能填写0~a[d]范围内的数 
    for (int t = 0; t <= 9; t++) {
        if (flag) {
            if (t > a[d])break;
            else if (t == a[d]) {
                //往下一位搜索的时候,需要传递【之前的每一位都和n的对应位置一致】这一信息 
                res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 1);
            } else {
                //这一位数字比N小,之后的数字可以任意枚举 
                res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 0);
            }
        } else {
            //高位数字均比N小,之后的数字可以任意枚举 
            res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 0);
        }
    }
    return dp[d][cur][rmd][flag] = res;

}

int main() {
    cin >> n;
    for (int i = 0; i < n.size(); i++) {
        a[i] = n[i] - '0';
    }
    ll ans = 0;
    for (int s = 1; s <= n.size() * 9; s++) {
        memset(dp, -1, sizeof(dp));
        ans += dfs(s, 0, 0, 0, 1);
    }
    cout << ans << endl;
    return 0;
}

F.Rotation Puzzle(折半搜索)

题意:

有一个 H H H W W W列的格栅图,对应着数字 1 1 1 H × W H\times W H×W,即格子 ( i , j ) (i,j) (i,j)对应着 1 ≤ S i , j ≤ H × W 1\le S_{i,j}\le H\times W 1Si,jH×W。我们现在希望在若干次操作以后,能够使得 S i , j = ( i − 1 ) × W + j S_{i,j}=(i-1)\times W+j Si,j=(i1)×W+j即从左到右、从上到下,依次为 1 , 2 , … , H × W 1,2,\dots,H\times W 1,2,,H×W。你所能做的操作是这样的:

  1. 首先,在格栅图中,选定一个大小为 ( H − 1 ) × ( W − 1 ) (H-1)\times(W-1) (H1)×(W1)的矩形范围;
  2. 将其中的内容进行 180 ° 180° 180°旋转,例如:
1 2 3    对应:     6 5 4
4 5 6   ------>    3 2 1

如果你能在 20 20 20次操作内将矩阵转换成理想状态,则输出最少的步数,否则输出 − 1 -1 1

分析:

根据题目所说的旋转区域大小,我们可以认为只存在 4 4 4种方案,分别对应左上角、左下角、右上角、右下角,对应 20 20 20步,就是 4 × 3 19 4\times3^{19} 4×319(因为连续旋转同一个区域 2 2 2次是没有意义的),这个数字超过了 4 × 1 0 9 4\times10^9 4×109,仍然会超时。

我们注意到这个过程中指数有很大的影响,此时我们想到可以使用折半搜索:因为我们知道起始状态,也知道目标状态,那么我们可以分别将起始状态向后进行变化,将目标状态向前进行还原,此时对于起始状态,它进行 10 10 10次旋转对应的方案数为 4 × 3 9 4\times3^9 4×39,不超过 × 1 0 5 \times10^5 ×105种可能,此时时间复杂度就大大下降了。在枚举过程中,我们使用广度有限搜索,记录步数与对应的旋转结果。

在实现旋转过程时,假设 ( i , j ) (i,j) (i,j) ( k , l ) (k,l) (k,l)对应,那么我们有: i + k = 2 ∗ x + h − 2 i+k=2*x+h-2 i+k=2x+h2,以及 j + l = 2 ∗ y + w − 2 j+l=2*y+w-2 j+l=2y+w2,其中 ( x , y ) (x,y) (x,y)是旋转区域左上角的下标。这样以来我们经过代数计算即可找到旋转后的对应位置。如果你对这部分内容感到不理解,可以参考下面的说明图。颜色一样代表他们相对应。

代码:

#include<bits/stdc++.h>

using namespace std;

struct node {
    int step = 0, dir = -1;
    vector<vector<int>> state;

    node() {}

    node(vector<vector<int>> _state, int _step, int _dir) {
        state = _state;
        step = _step;
        dir = _dir;
    }
};

int h, w;
//下面这句话的含义为:创建一个h行w列,名为original的二维动态数组 
vector<vector<int>> original(8, vector<int>(8));
vector<vector<int>> target(8, vector<int>(8));
int d[4][2] = {{0, 0},
               {0, 1},
               {1, 0},
               {1, 1}}; //代表旋转区域的左上角位置 
map<vector<vector<int>>, int> res[2];


vector<vector<int>> rotate(vector<vector<int>> before, int flag) {
    //根据数量关系进行旋转 
    vector<vector<int>> after(h, vector<int>(w));
    int x = d[flag][0], y = d[flag][1];
    int sum_i = x * 2 + h - 2, sum_j = y * 2 + w - 2;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            if (i >= x && i <= x + h - 2 && j >= y && j <= y + w - 2) {
                //进行旋转 
                after[i][j] = before[sum_i - i][sum_j - j];
            } else after[i][j] = before[i][j]; //其他位置不变 
        }
    }
    return after;
}

void bfs(vector<vector<int>> st, int rotate_target_flag) {
    queue<node> Q;
    Q.push(node(st, 0, -1));
    res[rotate_target_flag][st] = 0;
    vector<vector<int>> cur;
    while (!Q.empty()) {
        node cur = Q.front();
        Q.pop();
        for (int i = 0; i < 4; i++) {
            if (i == cur.dir)continue;
            vector<vector<int>> rotate_res = rotate(cur.state, i);
            if (!res[rotate_target_flag].count(rotate_res)) {
                //注意步骤限制,防止超时 
                if (cur.step < 10)Q.push(node(rotate_res, cur.step + 1, i));
                res[rotate_target_flag][rotate_res] = cur.step + 1;
            }
        }
    }
}

int main() {
    cin >> h >> w;
    for (int i = 0; i < h; i++) {
        //此处为了简化,我们将下标换成从0开始 
        for (int j = 0; j < w; j++) {
            cin >> original[i][j];
            target[i][j] = i * w + j + 1;
        }
    }
    bfs(original, 0);
    bfs(target, 1);
    int ans = 21;
    for (auto cur: res[0]) {
        vector<vector<int>> state = cur.first;
        if (res[1].count(state)) {
            ans = min(ans, res[0][state] + res[1][state]);
        }
    }
    if (ans > 20)cout << -1 << endl;
    else cout << ans << endl;
    return 0;
}

学习交流

以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值