AtCoder Beginner Contest 351 A-F题解

本文是AtCoder比赛ABC351的赛题解析,涵盖A - F共6道题。涉及循环、模拟、数组、栈、搜索、思维、扫描线、树状数组、线段树等多种算法和数据结构知识,给出各题题意、题解思路及代码实现方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

比赛链接https://atcoder.jp/contests/abc351
比赛时间:2024 年 4 月 27 日 20:00-21:40

A题:The bottom of the ninth

标签:循环、模拟
题意:给定 A A A 9 9 9局的得分和 B B B 8 8 8局的得分。求 B B B队的第 9 9 9局要得多少分,才能总得分超过 A A A队。
题解:分别求出两队的总得分,如果 B B B队当前总得分已经超过 A A A队了,输出 0 0 0;否则输出相差的分数值 + 1 +1 +1(因为要超过)。
代码

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

int main() {
    // A队和B队 分别的得分之和
    int sum1 = 0, sum2 = 0, x;
    for (int i = 1; i <= 9; i++) {
        cin >> x;
        sum1 += x;
    }
    for (int i = 1; i <= 8; i++) {
        cin >> x;
        sum2 += x;
    }
    if (sum2 > sum1) cout << 0;
    else cout << sum1 - sum2 + 1;
    return 0;
}

B题:Spot the Difference

标签:数组、循环
题意:给定 N N Nx N N N的二维字符数组 A A A B B B。有一个坐标位置的字符不同,输出那个位置。
题解:输入两个二维数组,然后遍历每一个位置,看看字符是否相同,不同就输出那个位置坐标即可。
代码

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

char s1[105][105], s2[105][105];

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++)
    cin >> s1[i][j];

    for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++) {
        cin >> s2[i][j];
        if (s1[i][j] != s2[i][j]) {
            cout << i << " " << j;
            return 0;
        }
    }
    return 0;
}

C题:Merge the balls

标签:栈
题意:给定 n n n个球,第 i i i个球的大小是 2 a i 2^{a_i} 2ai。轮流将这 n n n个球加到一个序列中,一开始序列为空。每加一个球,如果序列的最后一个球和倒数第二个球大小相同,就将这两个球移除,然后加入一个新球,新球的大小是这两个球大小之和。不断地进行操作直到序列最后两个球不同或者序列中只有一个球了停止,然后再不断地加入球,直到这 n n n个球都被加入了。求最后序列中剩下的球的个数。
题解:我们分析下样例,比如以下这个样例:
7
2 1 1 3 5 3 3
加入到第 3 个球的时候,形成 2 1 1,最后两个相同了,删掉这两个,然后加入一个 2,形成 2 2;最后两个又相同了,删掉这两个,然后加入一个 3。
加入第 4 个球的时候,形成 3 3,最后两个相同了,删掉这两个,然后加入一个 4,形成 4。
加入第 5 个球,形成 4 5。
加入第 6、7 个球,形成 4 5 3 3,最后两个相同了,删掉最后两个,加入一个 4。
所以最后序列中剩下 4 5 4。
注意:题目中输入的是球的指数,如果两个球大小相同都是 2 a i 2^{a_i} 2ai,那么相加就是 2 ∗ 2 a i = 2 a i + 1 2*2^{a_i}=2^{a_{i+1}} 22ai=2ai+1
我们可以通过 模拟不断地将最后的两个拿出来,然后进行合并,塞一个新的放到栈中的这个流程,不断进行这个操作,直到最后球都放完了,最终输出栈的大小即可。
代码

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

stack<int> s;
int n, x;

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x;
        s.push(x);
        while (s.size() >= 2) {
            int a = s.top(); s.pop();
            int b = s.top(); s.pop();
            if (a != b) {
                s.push(b);
                s.push(a);
                break;
            } else {
                s.push(a + 1);
            }
        }
    }
    cout << s.size() << endl;
    return 0;
}

D题:Grid and Magnet

标签:搜索
题意:给定一个 n n nx m m m的地图,地图中 # 表示不可以走,. 表示可以走。在此基础上加上一个限制,如果一个 . 周围上下左右中有一个位置有 #,就不能移动。求从 . 的地方走最多能到达的格子数。
题解:如果没有限制,就是普通的搜索了,暴力跑就好了。加上这个限制,如果我们像连通块一样,提前把某个格子跑到,并且因为 周围上下左右有一个#就不能移动,提前打上标记。后面其实从其他位置出发也能跑到这个格子,这个格子的计数就少了。
那如果我们跑每个点的时候,都对整张地图的 v i s vis vis 标记 m e m s e t memset memset 重置一下,那么总的时间复杂度又太大了。
这边可以引入一个类似标记第几个连通块的 i d id id,我们跑每个没有被标记的点,在跑的过程中发现如果能走到的下个点的 v i s vis vis标记和当前标记不一样,也让它跑下去,并打上当前 i d id id,这样就能统计到所有能够跑到的点。
代码

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

int n, m, cnt, ans = 0, num = 0;
char s[1005][1005];
int vis[1005][1005];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

bool check(int x, int y) {
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i], ny = y + dy[i];
        if (s[nx][ny] == '#') return 0;
    }
    return 1;
}

void dfs(int x, int y, int id) {
    cnt++;
    vis[x][y] = id;
    if (!check(x, y)) return ;
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
        // 这边不判s[nx][ny]=='#' 因为前面check有判过了
        if (vis[nx][ny] == vis[x][y]) continue;
        dfs(nx, ny, id);
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
    cin >> s[i][j];

    for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++) {
        if (s[i][j] == '.' && !vis[i][j]) {
            cnt = 0;
            ++num;
            dfs(i, j, num);
            ans = max(ans, cnt);
        }
    }

    cout << ans;
    return 0;
}

E题:Jump Distance Sum

标签:思维
题意:在坐标平面上,有 N N N个点 P 1 , P 2 , … , P N P_1,P_2,…,P_N P1,P2,,PN,其中点 P i P_i Pi的坐标为 ( X i , Y i ) (X_i,Y_i) (Xi,Yi)
两点 A A A B B B之间的距离 d i s t ( A , B ) dist(A,B) dist(A,B)定义如下:
一只兔子最初位于点 A A A。位置为 ( x , y ) (x,y) (x,y)的兔子可以跳跃到
( x + 1 , y + 1 ) 、 ( x + 1 , y − 1 ) 、 ( x − 1 , y + 1 ) (x+1,y+1) 、 (x+1,y−1) 、 (x−1,y+1) (x+1,y+1)(x+1,y1)(x1,y+1) ( x − 1 , y − 1 ) (x−1,y−1) (x1,y1)
d i s t ( A , B ) dist(A,B) dist(A,B)被定义为从 A A A点到 B B B点所需的最少跳跃次数。
如果经过任意次数的跳跃都无法从点 A A A到达点 B B B,则设为 d i s t ( A , B ) = 0 dist(A,B)=0 dist(A,B)=0
计算总和 ∑ i = 1 N − 1 ∑ j = i + 1 N dist ( P i , P j ) \displaystyle\sum_{i=1}^{N-1}\displaystyle\sum_{j=i+1}^N \text{dist}(P_i,P_j) i=1N1j=i+1Ndist(Pi,Pj)
题解:兔子的移动是对角移动,不太方便进行操作。我们可以把坐标轴旋转 45 % 45\% 45%,并缩放 2 \sqrt2 2 倍。那么 ( x , y ) (x,y) (x,y)点移动到 ( x + y , x − y ) (x+y,x-y) (x+y,xy)。比如 ( 2 , 2 ) (2,2) (2,2)变成 ( 2 2 , 0 ) (2\sqrt2,0) (22 ,0),放大 2 \sqrt2 2 倍,为 ( 4 , 0 ) (4,0) (4,0)
原来位置为 ( x , y ) (x,y) (x,y)的兔子可以跳跃到
( x + 1 , y + 1 ) 、 ( x + 1 , y − 1 ) 、 ( x − 1 , y + 1 ) (x+1,y+1) 、 (x+1,y−1) 、 (x−1,y+1) (x+1,y+1)(x+1,y1)(x1,y+1) ( x − 1 , y − 1 ) (x−1,y−1) (x1,y1)
我们转换后兔子可以从 ( x + y , x − y ) (x+y,x-y) (x+y,xy)跳跃到 ( x + y + 2 , x − y ) 、 ( x + y , x − y + 2 ) 、 ( x + y , x − y − 2 ) (x+y+2,x−y)、 (x+y,x−y+2) 、 (x+y,x−y−2) (x+y+2,xy)(x+y,xy+2)(x+y,xy2) ( x + y − 2 , x − y ) (x+y−2,x−y) (x+y2,xy)
转换成更通用的式子:兔子可以从 ( x , y ) (x,y) (x,y)跳跃到 ( x + 2 , y ) 、 ( x , y + 2 ) 、 ( x , y − 2 ) (x+2,y)、 (x,y+2) 、 (x,y−2) (x+2,y)(x,y+2)(x,y2) ( x − 2 , y ) (x−2,y) (x2,y)
那么我们观察到每次跳跃的距离都是偶数,也就是说如果 A = ( x 1 , y 1 ) , B = ( x 2 , y 2 ) A=(x_1,y_1),B=(x_2,y_2) A=(x1,y1)B=(x2,y2) ∣ x 1 − x 2 ∣ |x_1-x_2| x1x2不能被 2 2 2整除,或者 ∣ y 1 − y 2 ∣ |y_1-y_2| y1y2不能被 2 2 2整除,那么兔子就无法从 A A A到达 B B B
对于能到达的情况跳跃的最少次数显然等于 1 2 ( ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ ) \frac{1}{2}(\lvert x_1-x_2\rvert+\lvert y_1-y_2\rvert) 21(∣x1x2+y1y2∣)
也就是说对于能够相互到达的两个点 A A A B B B,他们的转化后的 x x x坐标的奇偶性和 y y y坐标的奇偶性必须相同。
我们可以统计一下对应转化后的 x x x奇/偶, y y y奇/偶的情况。然后去求一下对应情况下的跳跃次数。
这边举个例子:假设 x x x奇, y y y奇的情况,得到的 x x x的序列为
1     3     8     13 1\ \ \ 3\ \ \ 8\ \ \ 13 1   3   8   13
第二个 3 到 1 的距离为 1 段 2 的距离。
第三个 8 到前面两个点的距离为 2 段 8 的距离减去前缀前两个距离之和,即 2 ∗ 8 − ( 1 + 3 ) 2*8-(1+3) 28(1+3)
第四个 13 到前面两个点的距离为 3 段 13 的距离减去前缀 3 个距离之和,即 3 ∗ 13 − ( 1 + 3 + 8 ) 3*13-(1+3+8) 313(1+3+8)
注意题目中,转化后是 2 2 2的距离跳的,所以跳跃的次数最后要除以 2 2 2
代码

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

typedef long long ll;
ll n, X, Y, ans = 0;
vector<ll> x[2][2], y[2][2];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> X >> Y;
        x[(X+Y)%2][abs(X-Y)%2].push_back(X+Y);
        y[(X+Y)%2][abs(X-Y)%2].push_back(X-Y);
    }

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            int nx = x[i][j].size();
            int ny = y[i][j].size();
            if (nx == 0) continue;

            sort(x[i][j].begin(), x[i][j].end());
            sort(y[i][j].begin(), y[i][j].end());

            ll prex = x[i][j][0], prey = y[i][j][0];;
            for (int k = 1; k < nx; k++) {
                ans += k * x[i][j][k] - prex;
                prex += x[i][j][k];
            }
            for (int k = 1; k < ny; k++) {
                ans += k * y[i][j][k] - prey;
                prey += y[i][j][k];
            }
        }
    }

    cout << ans / 2;
    return 0;
}

F题:Double Sum

标签:扫描线、树状数组、线段树
题意:给定序列 A A A,计算 ∑ i = 1 N ∑ j = i + 1 N max ⁡ ( A j − A i , 0 ) \displaystyle \sum_{i=1}^N \sum_{j=i+1}^N \max(A_j - A_i, 0) i=1Nj=i+1Nmax(AjAi,0)
题解:观察公式,我们按照顺序遍历序列 A A A中的数,固定 A j A_j Aj,就变成了求当前数字和前面所有比自己小的数字之差的和。即公式: ∑ i = 1 j − 1 A j − A i \displaystyle \sum_{i=1}^{j-1} A_j - A_i i=1j1AjAi在满足条件 A j > A i A_j\gt A_i Aj>Ai的情况下。
我们将这个式子展开: A j ∑ i = 1 j − 1 − ∑ i = 1 j − 1 A i A_j\displaystyle \sum_{i=1}^{j-1} - \displaystyle \sum_{i=1}^{j-1}A_i Aji=1j1i=1j1Ai在满足条件 A j > A i A_j\gt A_i Aj>Ai的情况下。
通俗点讲,假设对于 A j A_j Aj来说,在它前面有 m m m个比它小的数 A i A_i Ai,那么值就是 m m m A j A_j Aj之和减去所有在第 j j j个数之前比它小的 A i A_i Ai之和。这个东西可以通过树状数组或者线段树进行维护。这题数据量比较大,需要离散化处理下。
代码

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

typedef long long ll;
const ll N = 4e5 + 10;
ll n, b[N], num[N], sum[N], ans = 0;
struct node {
    ll a, id;
}p[N];

bool cmp1(node x, node y) {
    if (x.a == y.a) return x.id < y.id;
    return x.a < y.a;
}

bool cmp2(node x, node y) {
    return x.id < y.id;
}


ll lowbit(ll x) {
    return x & (-x);
}

void update(ll *tree, ll x, ll k) {
    for (ll i = x; i <= n; i += lowbit(i)) tree[i] += k;
}

ll query(ll *tree, ll x) {
    ll res = 0;
    for (ll i = x; i > 0; i -= lowbit(i)) res += tree[i];
    return res;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].a;
        p[i].id = i;
    }
    sort(p + 1, p + 1 + n, cmp1);
    for (int i = 1; i <= n; i++) b[p[i].id] = i;
    sort(p + 1, p + 1 + n, cmp2);
    for (int i = 1; i <= n; i++) {
        ans += p[i].a * query(num, b[i] - 1);
        ans -= query(sum, b[i] - 1);
        update(num, b[i], 1);
        update(sum, b[i], p[i].a);
    }
    cout << ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值