AtCoder Beginner Contest 336 A~F

A.Long Loong(格式输出)

题意:

给出一个正整数XXX,请你输出一个'L',然后XXX'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(二进制)

题意:

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

分析:

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

优化

直接使用函数计算答案,答案为log2lowbit(X)log_2^{lowbit(X)}log2lowbit(X)

Tips:系统封装了库函数log2()log2()log2()可用于计算222为底数的对数,lowbitlowbitlowbit则需自己实现

代码:

#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作为十进制上每一位数字的选择,问能组成的第NNN小的数字是多少(从小到大)。

分析:

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

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

代码:

#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)

题意:

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

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

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

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

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

分析:

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

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

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

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

依次计算得到pre,nxtpre, nxtpre,nxt数组,再枚举所有点为中心,此时选择第iii个数字为中心,能构造的最高金字塔高度即为min(pre[i],nxt[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)

题意:

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

分析:

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

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

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

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

而若flagflagflag条件不满足,则后面的位置上可以随意枚举任意的数。因此我们增添一个维度flagflagflag,来进行更新。当dddNNN的长度一致,且rmd=0rmd=0rmd=0(没有余数,是满足要求的数字),且cur=targetcur=targetcur=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(折半搜索)

题意:

有一个HHHWWW列的格栅图,对应着数字111H×WH\times WH×W,即格子(i,j)(i,j)(i,j)对应着1≤Si,j≤H×W1\le S_{i,j}\le H\times W1Si,jH×W。我们现在希望在若干次操作以后,能够使得Si,j=(i−1)×W+jS_{i,j}=(i-1)\times W+jSi,j=(i1)×W+j即从左到右、从上到下,依次为1,2,…,H×W1,2,\dots,H\times W1,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

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

分析:

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

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

在实现旋转过程时,假设(i,j)(i,j)(i,j)(k,l)(k,l)(k,l)对应,那么我们有:i+k=2∗x+h−2i+k=2*x+h-2i+k=2x+h2,以及j+l=2∗y+w−2j+l=2*y+w-2j+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,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值