AtCoder Beginner Contest 335 A~F

A.2023(字符串)

题意:

给出一个以2023为结尾的字符串,要求你把该字符串的结尾修改为2024

分析:

本题解法较多,可以先输出前 s . l e n g t h ( ) − 1 s.length() - 1 s.length()1个字符,然后单独输出4,也可以修改最后一个字符,再整体输出。

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    string s;
    cin >> s;
    s[s.size() - 1] = '4';
    cout << s << endl;
    return 0;
}

B.Tetrahedral Number(枚举)

题意:

给出一个数字 N N N,要求输出所有满足 x + y + z ≤ N x + y + z \le N x+y+zN的三元组 ( x , y , z ) (x, y, z) (x,y,z)

分析:

使用三层循环枚举三元组即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin >> n;
    for (int i = 0; i <= n; i++) {
        for (int j = 0; i + j <= n; j++) {
            for (int k = 0; i + j + k <= n; k++) {
                cout << i << ' ' << j << ' ' << k << endl;
            }
        }
    }
    return 0;
}

C.Loong Tracking(思维)

题意:

n n n个点,开始时第 i ( i = 1 , 2 , . . . , n ) i(i = 1, 2, ..., n) i(i=1,2,...,n)个点的位置为 ( i , 0 ) (i, 0) (i,0)

题目将给出 Q Q Q个操作,操作分为以下两种:

  • 1 C: 将序号为1的点按C方向移动一格,且所有序号为 j j j的格子也会随着移动,即序号为 j ( j = 2 , 3 , . . . , n ) j(j = 2, 3, ..., n) j(j=2,3,...,n)的格子会来到操作前序号为 j − 1 j - 1 j1的格子所在的位置,其中 C ∈ ( R , L , U , D ) C \in (R, L, U, D) C(R,L,U,D),且 ( R , L , U , D ) (R, L, U, D) (R,L,U,D)分别代表右,左,上,下。

  • 2 p: 找到当前序号为 p p p的点所在的位置,并将该位置输出。

分析:

如果按要求进行模拟,那么时间复杂度就会来到 O ( N Q ) O(NQ) O(NQ),无法通过本题。

观察样例后,可以想到,既然移动的永远只有序号为 1 1 1的点,那么可以先将所有点所在的位置存在vector中,然后在每次移动操作后将序号为 1 1 1的点当前所在的位置也存入vector中,那么对于此时对于每个查询,均有一个长度为 n + q ( q 为移动次数 ) n + q(q\text{为移动次数}) n+q(q为移动次数)vector,且所有点均会沿着前面的点走过的路径进行移动,因此,可以通过序号+移动次数直接得到当前位置所在的下标。

为了便于处理,将序号倒着存放,此时序号为 i i i的点开始时所在的下标为 n − i ( 下标从0开始存放 ) n - i(\text{下标从0开始存放}) ni(下标从0开始存放),每次询问需要输出的元素对应的下标为 n − i + c n t n - i + cnt ni+cnt,其中 c n t cnt cnt为移动操作的次数。

代码:

#include<bits/stdc++.h>
using namespace std;

int n, q;
vector<int> x, y;

void init() {//初始化,将开始时所有点的坐标放入vector
    for (int i = n; i >= 1; i--) {
        x.push_back(i);
        y.push_back(0);
    }
}
int main(){
    cin >> n >> q;
    init();
    int cnt = 0;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            char c;
            cin >> c;
            if (c == 'U') {
                x.push_back(x.back());
                y.push_back(y.back() + 1);
            } else if (c == 'D') {
                x.push_back(x.back());
                y.push_back(y.back() - 1);
            } else if (c == 'L') {
                x.push_back(x.back() - 1);
                y.push_back(y.back());
            } else {
                x.push_back(x.back() + 1);
                y.push_back(y.back());
            }
            cnt++;
        } else {
            int id;
            cin >> id;
            cout << x[n - id + cnt] << ' ' << y[n - id + cnt] << endl;
        }
    }
    return 0;
}

D.Loong and Takahashi(构造)

题意:

给出一个 N × N N \times N N×N的网格,其中 N N N为奇数,你需要按以下要求在网格内填充内容:

  • 字母T必须在网格正中间。

  • 任意一个其他的网格均需填下数字 ( 1 ∼ N 2 − 1 ) (1 \sim N^{2} - 1) (1N21)

  • 所有填在网格中的数字,需满足值为 i i i所在的格子与值为 i + 1 i + 1 i+1的网格相邻(四方向)。

分析:

按要求进行模拟,从最外圈开始进行右,下,左,上四方向填数即可。

代码:

#include<bits/stdc++.h>
using namespace std;

int n, ans[50][50];

int check(int x, int y) {
    if (x < 1 || x > n || y < 1 || y > n || ans[x][y]) return 0;
    return 1;
}

int main(){
    cin >> n;
    int x = 1, y = 1, cnt = 1;
    while (cnt < n * n) {
        while (check(x, y)) {
            ans[x][y++] = cnt++;
        }
        y--;
        x++;
        while (check(x, y)) {
            ans[x++][y] = cnt++;
        }
        x--;
        y--;
        while (check(x, y)) {
            ans[x][y--] = cnt++;
        }
        y++;
        x--;
        while (check(x, y)) {
            ans[x--][y] = cnt++;
        }
        x++;
        y++;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (j != 1) cout << ' ';
            if (ans[i][j] == 0) cout << 'T';
            else cout << ans[i][j];
        }
        cout << endl;
    }
    return 0;
}

E - Non-Decreasing Colorful Path(并查集,DP)

题意

有一个“无向图”,上面有 N N N个结点和 M M M条边。每一个结点 V V V都有一个对应的权值 A V A_V AV。我们希望从第 1 1 1个结点出发,到达第 N N N个结点,在这个过程中,路上的每个点只经过一次,并且尽可能获得更多的分数。获得分数的规则是这样的:

  • 在每一次移动时,如果我们要从结点 U U U移动到结点 V V V,则必须满足 A U ≤ A V A_U\le A_V AUAV
  • 如果从 1 1 1号点到 N N N号点的所有路径中,没有任意一条满足这个要求,那么分数即为 0 0 0
  • 如果存在这样的路径,那么我们观察路径上的点的权值,去重以后的个数即为分数。

以题目中的样例1为例, N = 5 N=5 N=5时,通过1->3->4->5到达 N N N号点,这四个点的权值去重后共有4个数,因此分数为4。若某条路径路过 4 4 4个点,但对应的权值分别为 10 , 10 , 30 , 40 10,10,30,40 10,10,30,40,则分数应为 3 3 3,因为两个 10 10 10重复了。

此处需要注意的是,这个图可能非常大( 2 ≤ N ≤ 2 × 1 0 5 2\le N \le 2\times10^5 2N2×105)。

思路

在到达结点 N N N的过程中,路过的点的权值组成的序列,一定是不下降序列,我们的目标是让这个序列去重以后的长度尽可能长,那么我们可以认为,相邻的、权值相同的点可以被合并为同一个点,这个合并的过程可以使用并查集来完成。

在经过这样的压缩以后,整张图就变成了一张有向无环图(DAG)。此时你就可以使用带有备忘录的dfs来解决问题了。我们从 1 1 1号点出发进行深度优先遍历,并从 N N N号结点向前回溯,对于当前结点 c u r cur cur来说,我们用 d p [ c u r ] dp[cur] dp[cur]表示从它出发,到达 N N N号结点所遍历的节点数(包括 c u r cur cur自己)。

为了计算 d p [ c u r ] dp[cur] dp[cur]的值,我们遍历所有他可以到达的结点 v v v d p [ c u r ] = max ⁡ { d p [ v ] } + 1 dp[cur]=\max\{dp[v]\}+1 dp[cur]=max{dp[v]}+1 d p [ n ] = 1 dp[n]=1 dp[n]=1。当然, d p [ c u r ] dp[cur] dp[cur]的值只有在大于 0 0 0时才有意义。

代码

#include<bits/stdc++.h>

using namespace std;
const int maxn = 2e5 + 10, minn = -1e8;
int n, m, u, v, a[maxn], fa[maxn], dp[maxn], flag;
vector<int> G[maxn];
vector<pair<int, int>> e;

int dfs(int x) {
    if (fa[x] == x)return x;
    else return fa[x] = dfs(fa[x]);
}

void merge(int x, int y) {
    fa[dfs(x)] = dfs(y);
}

void solve(int cur) {
    if (dp[cur] != minn)return;
    if (cur == dfs(n)) {
        //dp[cur]表示从cur出发到n号点,会途径路过多少个点,注意是包括cur的
        dp[cur] = 1;
        flag = 1; //标记:存在这样的道路
        return;
    }
    for (auto v: G[cur]) {
        solve(v);
        dp[cur] = max(dp[cur], dp[v] + 1);
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        fa[i] = i;
        dp[i] = minn;
    }
    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        if (a[u] == a[v])merge(u, v); // 此时u和v可以看成同一个点,进行合并
        else e.push_back(make_pair(u, v));
    }
    for (auto p: e) {
        // 必须先处理合并,再去加入其他的边
        u = p.first;
        v = p.second;
        if (a[u] < a[v])G[dfs(u)].push_back(dfs(v));
        else G[dfs(v)].push_back(dfs(u));
    }
    flag = 0;
    solve(dfs(1));
    if (flag) cout << dp[dfs(1)] << endl;
    else cout << 0 << endl;
    return 0;
}

F - Hop Sugoroku(DP)

题意

n n n个数 A 1 , A 2 , … , A n A_1, A_2, \dots, A_n A1,A2,,An,对应 n n n个正方形。起初,你把棋子放在 1 1 1号格子的位置,接下来你可以重复以下步骤:

  • 棋子可以从第 i i i个位置移动到第 i + A i × x i+A_i\times x i+Ai×x的位置,即:若你在第 i i i个位置,你就可以向后移动任意 A i A_i Ai x x x倍。 x x x可以是任意一个正整数,但这个位置不能超过 N N N
  • 你可以在任意一个位置停下来。

现在问你,你最多可以走多少种不同的路线?答案需要对998244353取模。

思路

题目中, A A A数组中所有的元素都是正整数,所以移动的过程方向不变。一个简单的思路是用 d p [ x ] dp[x] dp[x]表示从 x x x出发,向后的路线条数,那么我们可以用伪代码表示:

dp[1] = 1; //初始化为1
for (int i = 1; i <= n; i++) {
    for (int j = i + A[i]; j <= n; j += A[i]) {
        dp[j] = (dp[j] + dp[i]) % mod; //从i出发可以到达j,因此到达j的方案数应当被更新
    }
}

最后, ∑ i = 1 n d p [ i ] \sum\limits_{i=1}^{n}dp[i] i=1ndp[i]对mod取模即为最终答案。当 A A A数组中的数很大的时候,这个算法的时间复杂度接近线性级别,但如果数组中的数都不大时,这个算法的时间复杂度就是平方级别,而本题数据量也很大( 1 ≤ N ≤ 2 × 1 0 5 1\le N \le 2\times 10^5 1N2×105),这样的方法显然是无法通过所有样例的。

一个可能的思路是采用分治,根据A数组中元素的大小分开讨论,若 A [ i ] A[i] A[i]很大,就按照上面的循环直接进行。否则,若 A [ i ] A[i] A[i]比较小,我们利用 d p [ i ] = ∑ i ≡ j ( m o d A j ) d p [ j ] dp[i]=\sum\limits_{i\equiv j\pmod{A_j}}dp[j] dp[i]=ij(modAj)dp[j]这一性质,维护一个 f f f数组, f [ x ] [ y ] f[x][y] f[x][y]表示:所有对 y y y取模,得到的结果是 x x x的元素都需要标记的值。

那么,怎么区分所谓的“很大”和“比较小”呢?这里我们以 n \sqrt{n} n 为分界线,根据数组的大小可以知道,时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )。这里的 n \sqrt{n} n 可以根据每个输入值灵活改变,也可以根据 n n n可能的最大值,将分界线直接设定为 500 500 500(约等于 2 × 1 0 5 \sqrt{2\times10^5} 2×105 )。

代码

#include<bits/stdc++.h>

using namespace std;
const int maxn = 2e5 + 10, maxm = 510, mod = 998244353;
int a[maxn], dp[maxn], f[maxm][maxm];

int main() {
    int n;
    cin >> n;
    int m = sqrt(n);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    dp[1] = 1;
    int res = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // 加上之前累计的标记值
            dp[i] = (dp[i] + f[j][i % j]) % mod;
        }
        //根据当前的结果向后推算
        if (a[i] > m) {
            //相对比较大,直接推即可
            for (int j = i + a[i]; j <= n; j += a[i]) {
                dp[j] = (dp[j] + dp[i]) % mod;
            }
        } else {
            f[a[i]][i % a[i]] += dp[i];
            f[a[i]][i % a[i]] %= mod;
        }
        res = (res + dp[i]) % mod; //统计结果
    }
    cout << res << endl;
    return 0;
}

学习交流

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值