2024-09-07 22:21阅读: 748评论: 2推荐: 1

AtCoder Beginner Contest 370

A - Raise Both Hands (abc370 A)

题目大意

给出Snuke举的左右手情况,如果只举左手,输出Yes,如果只举右手,输出No,否则输出Invalid

解题思路

逐一判断即可。

神奇的代码
#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 l, r;
cin >> l >> r;
if (l == 1 && r == 0)
cout << "Yes" << '\n';
else if (l == 0 && r == 1)
cout << "No" << '\n';
else
cout << "Invalid" << '\n';
return 0;
}


B - Binary Alchemy (abc370 B)

题目大意

给定物品合成成分表aij表示物品 i和物品 j合成物品 aij

问物品 1,依次与 1,2,3,..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;
vector<vector<int>> a(n);
for (int i = 0; i < n; i++) {
a[i].resize(i + 1);
for (auto& x : a[i]) {
cin >> x;
--x;
}
}
int cur = 0;
for (int i = 0; i < n; ++i) {
int x = cur, y = i;
if (x < y)
swap(x, y);
cur = a[x][y];
}
cout << cur + 1 << '\n';
return 0;
}


C - Word Ladder (abc370 C)

题目大意

给定两个字符串s,t

用最小的次数,使得 s=t,并且字符串x的字典序最小。

操作为,选择 si=c,并且将修改后的 s放入 x的末尾。

解题思路

如何次数最小呢?

依次考虑s从左到右的每一位 i,如果 siti,那我肯定要 si=ti,但这是我们此时要进行的操作吗?还是先放一放,改后面的字母后,再改当前位?

由于每次会将修改后的s放入 x的末尾,因此我们要优先考虑首先进行的操作,应该是:即刻进行,还是缓一缓在进行。

如果si>ti,那就优先更改当前位,这样改后的 s的字典序更小。

如果 si<ti,那就先更改后面位的,最后再改当前位,这样得到的 x的字典序最小。

这种回溯的感觉,可以用DFS实现上述操作。

神奇的代码
#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);
string s, t;
cin >> s >> t;
vector<string> ans;
auto dfs = [&](auto dfs, int pos) {
if (pos == s.size()) {
return;
}
if (s[pos] == t[pos]) {
dfs(dfs, pos + 1);
} else if (s[pos] < t[pos]) {
dfs(dfs, pos + 1);
s[pos] = t[pos];
ans.push_back(s);
} else {
s[pos] = t[pos];
ans.push_back(s);
dfs(dfs, pos + 1);
}
};
dfs(dfs, 0);
cout << ans.size() << '\n';
for (auto& i : ans)
cout << i << '\n';
return 0;
}


D - Cross Explosion (abc370 D)

题目大意

二维网格,初始每个格子有墙。

依次进行q次放炸弹的操作,给定每次放炸弹的位置 (i,j),如果该位置有墙,则该墙消失。

否则,炸弹会爆炸,会产生十字冲击波,该位置上下左右的各第一个墙都会消失。

问最后还存在的墙的数量。

解题思路

对于第一种情况,直接移除该位置的墙即可。

对于第二种情况,需要找到该列上下、该行左右最近的墙。

墙的数量hw4e5,可以储存每个墙的坐标。

然后对于每行和每列,分别维护hset[i]表示第i行还有墙的列坐标,是个setwset[i]表示第 i列还有墙的行坐标 ,也是个set

这样,对于一个炸弹 (i,j),如果该位置没有墙(hset[i].find(j)==hset[i].end()),则需要找到 <j的最大和 >j 的最小的数字。同理对于wset也要找对应的数字,然后 erase。这样每次操作的复杂度都是 O(log),总的时间复杂度就是 O(qlog(h+w))

代码对于没有墙的逻辑是:

  • it=hset[i].lower_bound(j), 由于没有墙,此时一定 it>y(否则是 it==y),如果 it!=hset[i].end(),那么它就是下面的第一个墙(这里认为左上是原点),要毁掉,于是it=hset[i].erase(it)erase返回值是移除了该 it后的下一个元素。
  • 然后要找上面的第一个墙,此时 it>y的第一个位置(无论刚刚是否erase了),因此如果 it!=hset[i].begin(),那么 prev(it)就是上面的第一个墙,要毁掉,于是 hset[i].erase(prev(it))

同理的思路处理 wset即可。

神奇的代码
#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, q;
cin >> h >> w >> q;
vector<int> hh(h), ww(w);
iota(hh.begin(), hh.end(), 0);
iota(ww.begin(), ww.end(), 0);
vector<set<int>> hset(h), wset(w);
for (int i = 0; i < h; i++) {
hset[i].insert(ww.begin(), ww.end());
}
for (int i = 0; i < w; i++) {
wset[i].insert(hh.begin(), hh.end());
}
while (q--) {
int x, y;
cin >> x >> y;
--x, --y;
auto it = hset[x].lower_bound(y);
if (it != hset[x].end() && *it == y) {
hset[x].erase(y);
wset[y].erase(x);
} else {
if (it != hset[x].end()) {
wset[*it].erase(x);
it = hset[x].erase(it);
}
if (it != hset[x].begin()) {
it = prev(it);
wset[*it].erase(x);
hset[x].erase(it);
}
it = wset[y].lower_bound(x);
if (it != wset[y].end()) {
hset[*it].erase(y);
it = wset[y].erase(it);
}
if (it != wset[y].begin()) {
it = prev(it);
hset[*it].erase(y);
wset[y].erase(it);
}
}
}
int cnt = 0;
for (int i = 0; i < h; i++) {
cnt += hset[i].size();
}
cout << cnt << '\n';
return 0;
}


E - Avoid K Partition (abc370 E)

题目大意

给定一个数组a,划分成若干个子区间,使得没有子区间的和为 k

求划分方案数。

解题思路

朴素dp就是设 dp[i]表示前 i段划分满足条件的方案数。

转移则枚举最后一次的区间,然后 dp[i]=1jn,sum[j+1..i]kdp[j]

复杂度显然是 O(n2)的。

棘手在条件 sum[j+1..i]k上,如果没有这个条件,这个转移其实就是一个前缀和,用前缀和优化即为 O(n)

我们用前缀和相减代替区间和,即 sum[j+1..i]=sum[i]sum[j],转移式即为dp[i]=1ji,sum[i]sum[j]kdp[j]

换句话说,我们要对sum[j]sum[i]kdp[j]求和,这是个非常稀疏的条件,即如果设 cnt[i]=sum[j]=idp[j],即前缀和为 idp值,那上述转移式可改写成dp[i]=1j<idp[j]cnt[sum[i]k]

即一个前缀和与一个数的差值,这样转移就是O(1)了,因此维护一个dp前缀和 1j<idp[j]以及前缀和的dpcnt[i]=sum[j]=idp[j]即可。

时间复杂度就是O(nlogn)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
LL k;
cin >> n >> k;
vector<int> a(n);
for (auto& x : a)
cin >> x;
map<LL, int> cnt;
cnt[0] = 1;
LL presum = 0;
int precnt = 1;
int ans = 0;
for (auto& i : a) {
presum += i;
ans = (precnt - cnt[presum - k] + mo) % mo;
cnt[presum] = (cnt[presum] + ans) % mo;
precnt = (precnt + ans) % mo;
};
cout << ans << '\n';
return 0;
}


F - Cake Division (abc370 F)

题目大意

给定一个环形数组,划分为k段,使得每段和的最小值最大。

在该最大值的各种划分方案中,求有多少位置,在所有划分方案中都不被分开。

解题思路

如果我们确定了这个每段和的最小值x,且是一个链的情况,我们有个贪心的策略:从第一个数开始往右延伸,直到第一个不小于x的位置y,有sum[1..y]x,sum[1..y1]<x,它们就是一段,我们定义f(1)=y+1,如果不存在可行的话,f(1)=n+1f(1)=y+1表示分了一段区间[1,y+1)。如果能往复能分成k段则可行。时间复杂度是 O(n)O(klogn)。后者就是二分来找到每一段的最右端。

容易发现这个 x与是否可行具有单调性: x越大,越难可行, x越小,越容易可行。因此可以通过二分找到这个 x

然后考虑环的情况,一种处理方法是考虑每个起点,每个起点都做一次上述验证,如果存在一个起点满足上述要求则可行。这样验证的复杂度是 O(nklogn)

如何优化呢?

由于环形,我们拆成链,然后复制一份。注意到以每个位置i为起点,计算f(i)的值,可以通过O(n)的滑动窗口得到f。这就是分一段的区间,而如果分两段就是f(f(x))。注意到只要起点固定,它会延伸到哪里也固定了,不同段之间也相互独立。因此函数可以简单的复合起来,相比于一次一次分,优化方向就是以倍增的形式分段。

即二分了x后,预处理倍增数组 up[i][j]表示从 i开始,分了 2j段后的右边界 y,即 [i,y)包含了满足题意的 2j段。只要 yin,那么从 i开始就可行的。通过倍增数组来分k段,每个起点的验证复杂度就降为 O(logk)

最后求有多少位置是不会被断开的,就在枚举起点的时候,如果不可行,那么该起点与上一个数之间就不能断开(断开了就是以该数为起点,经求得是不可行的),因此后者就是不可行的起点数量。

总的时间复杂度就是O(nlognk)

神奇的代码
#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, k;
cin >> n >> k;
vector<int> a(n + n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
a[i + n] = a[i];
}
int N = n + n;
int l = 1, r = 2e9 + 8;
auto check = [&](int x) {
vector<array<int, 20>> up(N + 2);
up[N][0] = N + 1;
up[N + 1][0] = N + 1;
queue<int> windows;
int r = 0;
int sum = 0;
for (int i = 0; i < N; ++i) {
while (r < N && sum < x) {
windows.push(a[r]);
sum += a[r];
++r;
}
if (sum < x)
up[i][0] = N + 1;
else
up[i][0] = r;
sum -= windows.front();
windows.pop();
};
for (int i = 1; i < 20; ++i)
for (int j = 0; j < N + 2; ++j) {
up[j][i] = up[up[j][i - 1]][i - 1];
}
int cnt = 0;
for (int i = 0; i < n; ++i) {
int pos = i;
for (int j = 0; j < 20; ++j) {
if ((k >> j) & 1) {
pos = up[pos][j];
}
}
if (pos <= i + n) {
++cnt;
}
}
return cnt;
};
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
int cnt = check(l);
cout << l << ' ' << n - cnt << '\n';
return 0;
}


G - Divisible by 3 (abc370 G)

题目大意

如果一个数是好的,说明它的因子和能被3整除。

给定 n,m,问一个长度为 m的数组 a的数量,满足其各数的乘积不超过 n,且是好数。

解题思路

<++>

神奇的代码


本文作者:~Lanly~

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

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

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