2024-05-04 16:05阅读: 177评论: 0推荐: 0

AtCoder Beginner Contest 351

A - The bottom of the ninth (abc351 A)

题目大意

给定9ai8bi,问最后一个 b9是多少,使得 ai<bi

解题思路

答案就是aibi+1

神奇的代码
a = sum(map(int, input().split()))
b = sum(map(int, input().split()))
print(a - b + 1)


B - Spot the Difference (abc351 B)

题目大意

给定两个二维矩阵,仅一个位置元素不一样,找出那个位置。

解题思路

二维遍历一下就找到了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<string> a(n), b(n);
for (auto& x : a)
cin >> x;
for (auto& x : b)
cin >> x;
auto solve = [&]() {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (a[i][j] == b[i][j])
continue;
return make_pair(i, j);
}
}
return make_pair(-1, -1);
};
auto [x, y] = solve();
cout << x + 1 << ' ' << y + 1 << '\n';
return 0;
}


C - Merge the balls (abc351 C)

题目大意

给定一个队列,执行以下q次操作。

每次操作,队尾塞一个 大小为 2ai的球。然后重复以下操作:

  • 若队尾两个球的大小不同,结束该操作
  • 否则,移除队尾两个的两个球,放一个大小 2ai+1的球 。回到上述操作。

解题思路

直接模拟即可,因为每个球最多只会被移除一次,最多只有q个球,因此从对球的操作次数考虑时间复杂度的话,其时间复杂度是 O(q)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> team;
while (n--) {
int x;
cin >> x;
team.push_back(x);
while (team.size() > 1) {
int a = team.back();
int b = team[team.size() - 2];
if (a == b) {
team.pop_back();
team.pop_back();
team.push_back(a + 1);
} else {
break;
}
}
}
cout << team.size() << '\n';
return 0;
}


D - Grid and Magnet (abc351 D)

题目大意

给定一个二维网格,一些格子上有磁铁,当走到磁铁格子上下左右的格子后,就动不了了。

问从哪个格子出发,其可以到达的格子数最多。

注意不是一次出发能到达的格子的最大值,是好格子的数量,存在一条路径到达好格子。

解题思路

考虑朴素的做法,就是O((hw)2),即枚举起点,然后 BFS得到到达的格子的数量。由于 h,w103,这显然会超时。

O((hw)2)中,枚举起点花了 O(hw),每次 BFS花了 O(hw)

考虑枚举起点能否优化。会发现有些枚举起点是无用的。

假设磁铁周围的格子是终止格子,如果枚举的一个起点是非终止格子,且其左边也是个非终止格子,那这两个格子的答案应该是一样的。因为它们可以相互到达,没任何影响,所能到达的点的范围是一样的。

由此,对于这个非终止格子,和周围同样是非终止格子合并在一起,在这个区域内任选一个格子进行一次 BFS,就能得到该格子的答案, 且这也是这个区域内所有非终止格子的答案。

因此预处理出所有的终止格子非终止格子,然后从所有的未被访问过的非终止格子进行BFS,求得访问过的格子数量。注意终止格子在不同的BFS可以重复访问,但同个 BFS只能访问一次,所以 终止格子的访问状态在每次BFS之前要重置,为避免重置访问状态带来的额外开销,访问状态visit[i]就改为记录访问时间 time[i],这样根据访问时间,也能判断本次 BFS是否访问该格子,也免去了重置 visit[i]=0的开销。

由于所有非终止格子只会访问一次,每个终止格子最多只会访问三次(一个方向是磁铁,然后可能有三个方向到达此格子),因此最后的时间复杂度是O(hw)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int h, w;
cin >> h >> w;
vector<string> a(h);
for (auto& x : a)
cin >> x;
bool empty = false;
vector<vector<int>> stop(h, vector<int>(w, 0));
array<int, 4> dx = {1, 0, -1, 0};
array<int, 4> dy = {0, 1, 0, -1};
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (a[i][j] == '#') {
stop[i][j] = 1;
for (int k = 0; k < 4; k++) {
int ni = i + dx[k];
int nj = j + dy[k];
if (ni >= 0 && ni < h && nj >= 0 && nj < w) {
stop[ni][nj] = 1;
}
}
}
if (a[i][j] == '.') {
empty = true;
}
}
}
vector<vector<int>> visited(h, vector<int>(w, 0));
int ans = empty;
int tt = 0;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (visited[i][j] != 0 || stop[i][j]) {
continue;
}
++tt;
queue<pair<int, int>> q;
q.push({i, j});
visited[i][j] = tt;
int cnt = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
cnt++;
if (stop[x][y]) {
continue;
}
for (int k = 0; k < 4; k++) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 0 && nx < h && ny >= 0 && ny < w &&
visited[nx][ny] < tt) {
visited[nx][ny] = tt;
q.push({nx, ny});
}
}
}
ans = max(ans, cnt);
}
}
cout << ans << '\n';
return 0;
}


E - Jump Distance Sum (abc351 E)

题目大意

二维网格,n个点,一次可以往四个角的方向走。定义 dist(pi,pj) 为从pipj需要的最小步数。若不能到达,则假定步数为0

i=1nj=i+1ndist(pi,pj)

解题思路

考虑怎样的情况是不能到达的。容易发现,一个格子不能到达其上下左右相邻的格子。

更进一步的,对每个点(x,y),当移动时,观察 x+y的变化 ,会发现只有三种情况:2,0,+2,无论哪种,其 x+y的奇偶性不变。因此可以得到 x+y奇偶性不同的点无法相互到达。相同的点或许可以到达(实际上是可以的)。

先对所有点根据 x+y的奇偶性分类,考虑同类别的pi,pj,其 dist如何计算。

简单起见,考虑 (xi,yi)(xj,yj) ,且xj>xi,yj>yi。每次移动,对于每个座标上的值,都得 +1,1,假设 xjxi>yjyi,为了最小步数,那我肯定每次选择的动作,都会让 xi+1。至于 yi,如果每次也 yi+1,那最终就超过了 yj,因此中间需要一些 yi1,来避免超过 yj。即一开始先 (+1,+1),当 yi==yj后,就 (+1,+1)(+1,1)交替,以保持 yi==yj不变,同时 xixj

但这样最后能到达 (xj,yj)吗?就看是否满足 (+1,+1)(+1,1)数量相等。当yi==yj时,还需要执行 left=xjxi个步骤,如果 left是偶数,则(+1,+1)(+1,1)数量相等,可行。事实上,因为xi+yixj+yj奇偶性相同,并且 yi==yj,因此 xi,xj的奇偶性也相同,则 xjxi必定是偶数,因此也一定能到达 xj,yj)

由上述分析可以得知, dist(pi,pj)=max(|xixj|,|yiyj|)

朴素求和就是O(n2),棘手在max上,注意到上述距离实际上是切比雪夫距离,可以

绝对值去掉的方式比较套路,可以对 xi排序,然后按顺序遍历 xi,这样 xixj就始终是一个符号,但是外层还套了一个 max比较棘手。

注意到上述距离实际上是切比雪夫距离,可以将其转换成曼哈顿距离。得到新点(xi+yi2,xj+yj2),然后计算两点的曼哈顿距离,即两维度的差的绝对值的和。其中 x维度和 y维度是可以分开算的,因此就按照上述绝对值去掉的方式,维护前缀和,计算求和即可。

时间复杂度是O(n)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
array<vector<pair<int, int>>, 2> p{};
for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
p[(x + y) & 1].push_back({x + y, x - y});
}
auto solve_one = [&](vector<int>& x) -> LL {
sort(x.begin(), x.end());
LL ans = 0, sum = 0;
for (int i = 0; i < x.size(); i++) {
ans += 1LL * x[i] * i - sum;
sum += x[i];
}
return ans;
};
auto solve = [&](vector<pair<int, int>>& p) -> LL {
vector<int> x, y;
for (auto& [a, b] : p) {
x.push_back(a);
y.push_back(b);
}
return solve_one(x) + solve_one(y);
};
LL ans = solve(p[0]) + solve(p[1]);
ans /= 2;
cout << ans << '\n';
return 0;
}


F - Double Sum (abc351 F)

题目大意

给定n个数 ai,计算 i=1nj=i+1nmax(ajai,0)

解题思路

将求和式变一下,即为j=1ni<j,ai<ajajai=j=1ni<j,ai<ajaji<j,ai<ajai

即两个二维偏序问题,一个关于i<j,ai<aj的计数问题,一个关于i<j,ai<ajai求和问题。

二维偏序的解法,假想在一个二维坐标系内,就是问一个矩形的和。

可通过循环满足一个不等式关系(即满足一维),再用一个数据结构维护另一个不等式关系(维护另一维)求和。

以第一个偏序问题为例,即枚举下标j,把小于 j的加入到数据结构中(满足了 i<j),然后在满足小于aj范围求和,此为一个区间查询操作。故可以建一棵树状数组或线段树维护此查询操作。其下标的含义是aj而不是 j。由于这里的 aj高达 108,但数量不超过 105,因此需要事先离散化。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
// starting from 1
template <typename T> class fenwick {
public:
vector<T> fenw;
int n;
fenwick(int _n) : n(_n) { fenw.resize(n); }
inline int lowbit(int x) { return x & -x; }
void modify(int x, T v) {
for (int i = x; i < n; i += lowbit(i)) {
fenw[i] += v;
}
}
T get(int x) {
T v{};
for (int i = x; i > 0; i -= lowbit(i)) {
v += fenw[i];
}
return v;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
set<int> candidate;
for (auto& x : a) {
cin >> x;
candidate.insert(x);
}
map<int, int> rank;
for (auto x : candidate) {
rank[x] = rank.size() + 1;
}
fenwick<LL> presum(rank.size() + 1), cnt(rank.size() + 1);
LL ans = 0;
for (int i = 0; i < n; ++i) {
int pos = rank[a[i]];
ans += 1ll * a[i] * cnt.get(pos) - presum.get(pos);
cnt.modify(pos, 1);
presum.modify(pos, a[i]);
}
cout << ans << '\n';
return 0;
}


G - Hash on Tree (abc351 G)

题目大意

给定一棵有根树,根是1。点有权值ai,定义树的权值为f(1)

f的计算方式为:

  • i是叶子,则 f(i)=ai
  • 否则, f(i)=a(i)+json(i)f(j)

执行q次操作,每次操作修改一个点的权值,回答修改后的树的权值,即f(1)

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18172416

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(177)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.