AtCoder Beginner Contest 336题解

196 阅读4分钟

AtCoder Beginner Contest 336题解

比赛链接

1.F

题目的解法主要是基于双向BFS对时间复制度的优化。

在20移动的基础上如果采用bfs,时间复杂度是4204 ^{20},这种时间复制度显然是无法接受的。所以就采用双向BFS,一个从起点状态开始搜索,一个从终点状态开始搜索,双方各搜索十次。如何判断有解呢?这个看两者搜索是否会产生状态重叠。如果产生了状态重叠证明是可以在20步以内到达的。

using i64 = long long;
​
struct Node {
    vector<vector<int>> a;
    int cnt, op;
};
int n, m;
map<vector<vector<int>>, int> mp;
​
// 旋转x, y为左上角的矩阵180
vector<vector<int>> rotate(vector<vector<int>> a, int x, int y) {
    vector<vector<int>> b = a;
    for (int i = 1; i <= n - 1; i++) {
        for (int j = 1; j <= m - 1; j++) {
            b[i + x][j + y] = a[n - i + x][m - j + y];
        }
    }
    return b;
}
​
void print(vector<vector<int>> a) {
    cout << "------------------\n";
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            cout << a[i][j] << " ";
        cout << "\n";
    }
    cout << "------------------\n";
}
​
int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
​
    cin >> n >> m;
    vector<vector<int>> a(n + 1, vector<int>(m + 1)), b(n + 1, vector<int>(m + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            b[i][j] = i * m - m + j;
        }
    }
    
    // 先从初始状态开始搜索
    queue<Node> q;
    q.push({a, mp[a] = 0, -1});
    while (!q.empty()) {
        auto [now, cnt, op] = q.front();
        q.pop();
        if (cnt == 10)
            continue;
        for (int i = 0; i < 4; i++) {
            auto nowChange = rotate(now, i / 2, i % 2);
            if (mp.find(nowChange) == mp.end()) {
                mp[nowChange] = cnt + 1;
                q.push({nowChange, cnt + 1, i});
            }
        }
    }
​
    //再从结尾状态开始搜索。
    queue<Node> q1;
    q1.push({b, 0, -1});
    while (!q1.empty()) {
        auto [now, cnt, op] = q1.front();
        q1.pop();
        if (mp.find(now) != mp.end()) {
            cout << cnt + mp[now];
            return 0;
        }
        if (cnt == 10)
            continue;
        for (int i = 0; i < 4; i++) {
            auto nowChange = rotate(now, i / 2, i % 2);
            q1.push({nowChange, cnt + 1, i});
        }
    }
    cout << -1;
    return 0;
}

2.E

统计数字状态通过数位dp来解决问题。

简意大概就是小于N 的数字中多少数字满足数字大小可以整除数位和。

基于数位dp,设dp[i][j][k]表示从高位算起,第i位数字,当前数字和为j, 取模结果为k的数字有多少。

这样就只需要枚举取模的数字大小,取模的数字大小最多为 位数*9。然后考虑枚举数位和。找到取模当前数位和为0的数字个数,最后累加即可。

具体代码比较重要的两处有注解。

using i64 = long long;
​
i64 n, m, cnt = 0;
i64 a[20], dp[20][10 * 20][10 * 20];
​
i64 dfs(i64 pos, i64 sum, i64 mod, i64 flag) {
    if (sum > m)
        return 0;
    if (pos == 0)
        return sum == m && mod == 0;
​
    if (!flag && dp[pos][sum][mod] != -1)
        return dp[pos][sum][mod];
    i64 x = flag ? a[pos] : 9;
    i64 ans = 0;
    for (i64 i = 0; i <= x; i++) {
        // 这个取模的拿个数字举个例子就明白了,比如说23 % 6 => 2 % 6 = 2 => (2 * 10 + 3) % 6 = 5
        ans += dfs(pos - 1, sum + i, (mod * 10 + i) % m, flag && (i == a[pos]));
    }
    if (!flag)
        dp[pos][sum][mod] = ans;
    return ans;
}
​
int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
​
    cin >> n;
    while (n) {
        a[++cnt] = n % 10;
        n /= 10;
    }
    i64 res = 0;
    for (int i = 1; i <= 9 * cnt; i++) {  // m考虑的是数位和的大小,最大为9 * 14
        m = i;
        memset(dp, -1, sizeof(dp));
        res += dfs(cnt, 0, 0, 1);
    }
    cout << res;
    return 0;
}

3.D

线性dp。考虑每个位置往左往右的最大长度,然后只能取其中最小的作为这个的点的金字塔长度。

两个dp数组,ldp, rdp,然后具体转移看代码的转移柿子。

const int N = 2e5 + 10;
int a[N], ldp[N], rdp[N];
signed main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
​
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
​
    for (int i = 1; i <= n; i++) {
        if (a[i] > a[i - 1])
            ldp[i] = ldp[i - 1] + 1;
        else
            ldp[i] = min({a[i], i, ldp[i - 1] + 1});
    }
    for (int i = n; i >= 1; i--) {
        if (a[i] > a[i + 1])
            rdp[i] = rdp[i + 1] + 1;
        else
            rdp[i] = min({a[i], n - i + 1, rdp[i + 1] + 1});
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        // cout << ldp[i] << ' ' << rdp[i] << '\n';
        ans = max(ans, min(ldp[i], rdp[i]));
    }
    cout << ans << '\n';
    return 0;
}

4.C

c是一个进制转换题目。

五进制的题,从0开始(题目0算第一个数),所以需要-1。

signed main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
​
    int n;
    cin >> n;
    n--;
    if (n == 0) {
        cout << 0;
        return 0;
    }
    vector<int> ans;
    while (n) {
        int tmp = n % 5;
        ans.push_back(tmp * 2);
        n /= 5;
    }
    for (int i = ans.size() - 1; i >= 0; i--) {
        cout << ans[i];
    }
    return 0;
}

5.B

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    vector<int> a;
    while (n) {
        int t = n % 2;
        a.push_back(n % 2);
        n /= 2;
    }
    int sum = 0;
    for (auto i : a) {
        if (i == 0)
            sum++;
        else
            break;
    }
    cout << sum << '\n';
    return 0;
}

6.A

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    cout << "L";
    for (int i = 0; i < n; i++) {
        cout << 'o';
    }
    cout << "n";
    cout << "g";
    return 0;
}