AtCoder Beginner Contest 351 A~F

A.The bottom of the ninth(思维)

题意

有两只队伍正在进行棒球游戏,每只队伍都有 9 9 9个回合,先手已经结束了所有的回合,并获得了 A 1 , A 2 , … , A 9 A_1, A_2, \ldots, A_9 A1,A2,,A9分数,而后手只进行了8轮,获得了 B 1 , B 2 , … , B 8 B_1, B_2, \ldots, B_8 B1,B2,,B8,问,后手最后一回合至少获得多少分数,才能保证赢下比赛?

分析

如果 ∑ i = 1 8 B i − ∑ i = 1 9 A i > 0 \sum\limits_{i = 1}^{8}B_i - \sum\limits_{i = 1}^{9}A_i > 0 i=18Bii=19Ai>0,那么最少获得 0 0 0分都能赢下比赛。

否则,需要的分数为 ∑ i = 1 9 A i − ∑ i = 1 8 B i + 1 \sum\limits_{i = 1}^{9}A_i - \sum\limits_{i = 1}^{8}B_i + 1 i=19Aii=18Bi+1

代码

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

void solve() {
    int a = 0, b = 0;
    for (int i = 1; i <= 9; i++) {
        int num;
        cin >> num;
        a += num;
    }
    for (int i = 1; i <= 8; i++) {
        int num;
        cin >> num;
        b += num;
    }
    if (b > a) cout << 0 << endl;
    else cout << a - b + 1 << endl;
}

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

B.Spot the Difference(循环)

题意

给出两个 N × N N \times N N×N的字符串矩阵,保证两个矩阵中只存在一个位置的字符是不相同的,输出这个不相同字符所在的位置。

分析

输入后遍历比较找到这个不同的字符即可。

代码

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

char a[105][105], b[105][105];

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> b[i][j];
            if (a[i][j] != b[i][j]) {
                cout << i << ' ' << j << endl;
                return;
            }
        }
    }
}

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

C.Merge the balls(栈)

题意

给出 N N N个球,其中第 i i i个球的大小为 2 A i 2^{A_i} 2Ai,数据中只会给出指数 A i A_i Ai

你将执行 N N N次操作,第 i i i次操作会将第 i i i个球放置在序列末尾,并执行以下操作:

  1. 如果序列中球的数量不超过1个,结束操作
  2. 如果序列最右边的两个球大小不同,结束操作
  3. 如果序列最右边的两个球大小相同,将这两个球删除,并将一个新的球加入序列中,这个新的球的大小为删除的两个球的大小之和,然后回到操作1.

问,经过这些操作后,序列中剩下多少球?

分析

每次操作序列最右边的两个球,不难想到可以使用栈来进行维护。

为了便于处理,每次操作不将数字放入栈后再进行判断,直接拿该球与栈顶元素比较,如果相同就删除栈顶元素,并增加小球大小,直到栈空或栈顶元素与当前小球大小不同,然后将小球放入栈中。

结束操作后,栈中元素数量即为最后的答案。

代码

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

const int MAXN = 2e5 + 5e2;

stack<int> St;

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a;
        while (!St.empty() && a == St.top()) {
            St.pop();
            a++;
        }
        St.push(a);
    }
    cout << St.size() << endl;
}

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

D.Grid and Magnet(bfs)

题意

给出一个 H × W H \times W H×W的网格,网格中包含以下内容:

  • ‘#’ : 磁铁
  • ‘.’ : 空地

由于你身穿钢铁盔甲,那么只要你当前位置的四方向上存在着磁铁,你就无法移动了。

给出自由度的定义为:从某个点出发,最多可以到达的点的数量(这些点可以是若干不同移动方案中到达的)

问,图中自由度最高的点对应的自由度是多少?

分析

可以将能到达的点分为两种:

  1. 该点到达后还能继续移动
  2. 该点到达后就会被磁铁吸住而无法移动

在搜索时,将满足条件 2 2 2的点也视为无法到达的点,并对所有满足条件 1 1 1的点搜索所在的连通块大小,并记录每个点的位置。

结束搜索后,就知道了当前连通块中包含的点的数量,而这些点能到达的会被磁铁吸住的点还未被计算到,因此,可以通过记录的每个点的位置,再去检查周围存在的满足条件 2 2 2的点的数量,由于可能会存在重复点,因此可以使用 s e t set set对这些满足条件 2 2 2的点坐标进行去重。

那么每次搜索得到的满足条件 1 1 1和条件 2 2 2的点的数量就是当前连通块中所有满足条件 1 1 1的点的自由度,记录每次搜索结果的最大值即可。

hint

只要图中存在'.',那么答案至少为 1 1 1

代码

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

const int N = 1e3 + 5e2;
char c[N][N];
int n, m, ans = 0, dp[N][N], vis[N][N];

int dx[] = {1, -1, 0, 0},
    dy[] = {0, 0, 1, -1};

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

queue<pair<int, int> > Q;
set<pair<int, int> > V;
set<pair<int, int> > S;
void bfs(int fx, int fy) {
    V.clear();
    S.clear();
    Q.push({fx, fy});
    vis[fx][fy] = 1;
    while (!Q.empty()) {
        auto u = Q.front();
        Q.pop();
        V.insert(u);
        for (int i = 0; i < 4; i++) {
            int x = u.first + dx[i],
                y = u.second + dy[i];
            if (x >= 1 && y >= 1 && x <= n && y <= m && check(x, y) && vis[x][y] == 0) {
                vis[x][y] = 1;
                Q.push({x, y});
            }
        }
    }
    for (auto u : V) {
        for (int i = 0; i < 4; i++) {
            int x = u.first + dx[i],
                y = u.second + dy[i];
            if (c[x][y] == '.' && vis[x][y] == 0) {
                S.insert({x, y});
            }
        }
    }
    int size = V.size() +  S.size();
    ans = max(ans, size);
}

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> c[i][j];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (vis[i][j] == 0 && c[i][j] == '.') {
                ans = max(ans, 1);
                if (check(i, j)) {
                    bfs(i, j);
                }
            }
        }
    }
    cout << ans << endl;
}

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

E.Jump Distance Sum(思维)

题意

给出二维坐标系上的 N N N个点 P 1 , P 2 , … , P N P_1, P_2, \ldots, P_N P1,P2,,PN,其中从每个点出发只能向左上,左下,右下,右上四方向移动,即点 ( x , y ) (x, y) (x,y)每次移动只能直接移动以下四个位置:

  • ( x − 1 , y − 1 ) (x - 1, y - 1) (x1,y1)
  • ( x + 1 , y − 1 ) (x + 1, y - 1) (x+1,y1)
  • ( x + 1 , y + 1 ) (x + 1, y + 1) (x+1,y+1)
  • ( x − 1 , y + 1 ) (x - 1, y + 1) (x1,y+1)

∑ i = 1 N − 1 ∑ j = i + 1 N d i s t ( P i , P j ) \sum\limits_{i = 1}^{N - 1}\sum\limits_{j = i + 1}^{N}dist(P_i, P_j) i=1N1j=i+1Ndist(Pi,Pj)

其中 d i s t ( P i , P j ) dist(P_i, P_j) dist(Pi,Pj)表示点 i i i经过若干次移动后到达点 j j j所需的步数,如果无法到达,则为 0 0 0

分析

将坐标轴旋转 4 5 ∘ 45^{\circ} 45,同时将 x , y x, y x,y坐标均变为原本 2 \sqrt{2} 2 倍,这样,点 ( x , y ) (x, y) (x,y)就被转化为了点 ( x + y , x − y ) (x + y, x - y) (x+y,xy)

例: 点 ( 3 , 3 ) (3, 3) (3,3)旋转后变为了点 ( 18 , 0 ) (\sqrt{18}, 0) (18 ,0) x , y x, y x,y坐标再变为原本的 2 \sqrt{2} 2 倍,就变为了 ( 6 , 0 ) (6, 0) (6,0)

完成了坐标转换后,原本四对角移动就变为了上下左右四方向移动,原本移动一次的距离为 2 \sqrt{2} 2 ,现在就变为了 2 2 2

那么从 ( x , y ) (x, y) (x,y)点出发,此时能直接移动到的四个点为: ( x + 2 , y ) , ( x − 2 , y ) , ( x , y + 2 ) , ( x , y − 2 ) (x + 2, y), (x - 2, y), (x, y + 2), (x, y - 2) (x+2,y),(x2,y),(x,y+2),(x,y2)

由于每次移动均是偶数距离,那么当两点的曼哈顿距离不为偶数时,就无法互相到达,因此,可以根据每个点到原点的曼哈顿距离的奇偶性分为两部分。

然后对于每部分点,只需要单独考虑 x , y x, y x,y坐标所需的移动次数,为了便于计算,可以将 x , y x, y x,y坐标均进行排序,就能保证所有前面的坐标均比当前坐标小,那么在计算时,就可以维护前缀和,通过前缀和快速计算出当前点与前面所有点的距离。

统计完所有点的距离后,由于当前每次移动的距离为 2 2 2,因此最后得到的总距离需要除以 2 2 2,才能得到最后的答案。

代码

#include<bits/stdc++.h>

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

vector<ll> P[5];

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        if ((x + y) % 2 == 0) {
            P[0].push_back(x + y);
            P[1].push_back(x - y);
        } else {
            P[2].push_back(x + y);
            P[3].push_back(x - y);
        };
    }
    sort(P[0].begin(), P[0].end());
    sort(P[1].begin(), P[1].end());
    ll pre = 0, ans = 0;
    for (int i = 0; i < 4; i++) {
        sort(P[i].begin(), P[i].end());
        int len = P[i].size();
        if (len) pre = P[i][0];
        for (ll j = 1; j < len; j++) {
            ans += j * P[i][j] - pre;
            pre += P[i][j];
        }
    }
    cout << ans / 2 << endl;
}

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

F.Double Sum(线段树)

题意

给出一个序列 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \ldots, A_N) A=(A1,A2,,AN),请你求出:

  • ∑ i = 1 N ∑ j = i + 1 N m a x ( A j − A i , 0 ) \sum\limits_{i = 1}^{N}\sum\limits_{j = i + 1}^{N}max(A_j - A_i, 0) i=1Nj=i+1Nmax(AjAi,0)

分析

观察计算公式可以发现,本题只需要将当前数字与前面所有比自己小的数字计算差并统计总和,那么假设数字 A i A_i Ai前面有 m m m个数字比 A i A_i Ai小,那么当前数字 A i A_i Ai能产生的价值即为 m × A i − ∑ j = 1 m B j m \times A_i - \sum\limits_{j = 1}^{m}B_j m×Aij=1mBj,其中 B j B_j Bj A A A数组中下标 1 ∼ i − 1 1 \sim i - 1 1i1之间比 A i A_i Ai小的数字。

因此,只要能快速计算出 ∑ j = 1 m B j \sum\limits_{j = 1}^{m}B_j j=1mBj,就能解决本道题目,而比 A i A_i Ai小的数字,不难想到这是一个区间,那么就可以使用线段树来维护区间内的每个数字的个数,以及数字的总和,这样就可以在 O ( l o g n ) O(logn) O(logn)的复杂度内计算得到 ∑ j = 1 m B j \sum\limits_{j = 1}^{m}B_j j=1mBj,即只需查询 1 ∼ A i − 1 1 \sim A_i - 1 1Ai1之间的数字总和即可。

由于题目给出 A i ≤ 1 0 8 A_i \le 10^{8} Ai108,而且线段树需要 4 4 4倍的内存空间,那么显然无法在规定的内存范围内完成本题,而数字的数量 N ≤ 4 × 1 0 5 N \le 4 \times 10^{5} N4×105,因此,可以通过将数字离散化的方式,就能在内存限制内完成。

因此,只需要遍历数组 A A A,每次检查前面所有比自己小的数字的个数以及数字之和,并通过公式计算出价值,然后将当前数字 A i A_i Ai更新到线段树中即可。

代码

#include<bits/stdc++.h>

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

struct Node {
    ll cnt, sum;
}T[N << 2];

int n, m;
ll id[N], a[N];

void pushup(int x) {
    T[x].cnt = T[x << 1].cnt + T[x << 1 | 1].cnt;
    T[x].sum = T[x << 1].sum + T[x << 1 | 1].sum;
}

void update(int l, int r, int x, int u) {
    if (l == r) {
        T[x].cnt++;
        T[x].sum += id[u];
        return;
    }
    int mid = l + r >> 1;
    if (u <= mid) update(l, mid, x << 1, u);
    else update(mid + 1, r, x << 1 | 1, u);
    pushup(x);
}

Node query(int l, int r, int x, int ql, int qr) {
    if (l >= ql && r <= qr) {
        return T[x];
    }
    int mid = l + r >> 1;
    Node num{0, 0};
    if (ql <= mid) {
        Node tmp = query(l, mid, x << 1, ql, qr);
        num.cnt += tmp.cnt;
        num.sum += tmp.sum;
    }
    if (qr > mid) {
        Node tmp = query(mid + 1, r, x << 1 | 1, ql, qr);
        num.cnt += tmp.cnt;
        num.sum += tmp.sum;
    }
    return num;
}

void solve() {
   cin >> n;
   for (int i = 1; i <= n; i++) {
       cin >> a[i];
       id[i] = a[i];
   }
   sort(id + 1, id + n + 1);
   m = unique(id + 1, id + n + 1) - id;
   ll ans = 0;
   for (int i = 1; i <= n; i++) {
       int num = lower_bound(id + 1, id + m + 1, a[i]) - id;
       Node pre{0, 0};
       if (num != 1)  pre = query(1, m, 1, 1, num - 1);
       ans += pre.cnt * a[i] - pre.sum;
       update(1, m, 1, num);
   }
   cout << ans << endl;
}

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

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值