AtCoder Beginner Contest 384
省流版
- A. 模拟转换即可
- B. 模拟判断即可
- C. 枚举所有情况排序即可
- D. 分区间和与跨区间和,后者为一个前后缀+区间和,分别判断即可
- E. 贪心选强度最小的粘液,优先队列维护即可
- F. 考虑除以\(2^k\),枚举\(k\),统计满足\(A_i + A_j = 0 \mod 2^k\)且\(A_i + A_j \neq 0 \mod 2^{k+1}\)的\(A_i + B_i\)的和,即做两次D题的做法即可。
A - aaaadaa (abc384 A)
题目大意
给定一个字符串\(S\)和两个字符\(c_1\)和\(c_2\),将所有非 \(c_1\) 替换为 \(c_2\)。
解题思路
依次枚举每个字符判断即可。
神奇的代码
_, a, b = input().strip().split()
c = input().strip()
d = ''.join([b if x != a else x for x in c])
print(d)
B - ARC Division (abc384 B)
题目大意
打比赛,根据当前rating
和表现分a
更新rating
。
两类比赛:
- Div1:
rating
在1600-2799之间,rating
增加a
- Div2:
rating
在1200-2399之间,rating
增加a
如果rating
不在范围内,不会增加rating
。
给了n
场比赛的rating
和a
,求最终rating
。
解题思路
按照题意模拟即可。
神奇的代码
#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, r;
cin >> n >> r;
while (n--) {
int d, a;
cin >> d >> a;
if (d == 1 && 1600 <= r && r <= 2799) {
r += a;
} else if (d == 2 && 1200 <= r && r <= 2399) {
r += a;
}
}
cout << r << '\n';
return 0;
}
C - Perfect Standings (abc384 C)
题目大意
给定五道题的分数,对于一个字符串,其分数为字符对应题目的分数之和。
共有 \(2^5 - 1=31\) 种情况,按照分数从高到低输出,如果分数相同,按照字典序输出。
解题思路
用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);
array<int, 5> a;
for (auto& x : a)
cin >> x;
vector<string> t1{""};
for (int i = 0; i < 5; ++i) {
vector<string> t2 = t1;
for (auto& x : t1) {
auto y = x + char('A' + i);
t2.push_back(y);
}
t2.swap(t1);
}
auto score = [&](const string& x) {
int res = 0;
for (auto c : x) {
res += a[c - 'A'];
}
return res;
};
sort(t1.begin(), t1.end(), [&](const string& x, const string& y) {
int sx = score(x), sy = score(y);
if (sx != sy)
return sx > sy;
return x < y;
});
t1.pop_back();
for (auto& x : t1)
cout << x << '\n';
return 0;
}
D - Repeated Sequence (abc384 D)
题目大意
给定一个 \(N\) 周期序列,其中前 \(N\) 项为 \(A=(A _ 1,A _ 2,A _ 3,\dotsc)\)。
判断是否存在一个和为 \(S\) 的非空连续子序列。
解题思路
所有数都是正数。
这个非空序列只有两种情况:
- 在一个周期内,很显然就是一个区间和的问题,转换成两个前缀和的差,用
set
存储前缀和判断是否存在即可。 - 跨越多个周期,此时就是一个后缀+一个前缀和若干个区间和的问题,枚举后缀,计算区间和数量,再判断对应前缀是否存在即可。
神奇的代码
#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;
LL s;
cin >> n >> s;
vector<LL> a(n);
for (auto& x : a)
cin >> x;
vector<LL> sum(a);
partial_sum(a.begin(), a.end(), sum.begin());
set<LL> st{0};
bool ok = false;
for (auto x : sum) {
if (st.count(x - s)) {
ok = true;
break;
}
st.insert(x);
}
if (!ok) {
reverse(a.begin(), a.end());
LL tot = accumulate(a.begin(), a.end(), 0ll);
LL sufsum = 0;
for (auto& i : a) {
sufsum += i;
LL left = (s - sufsum) % tot;
if (st.count(left)) {
ok = true;
break;
}
}
}
if (ok)
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}
E - Takahashi is Slime 2 (abc384 E)
题目大意
给定一个 \(H\times W\) 的网格,每个单元格中有一个整数 \(S _ {i,j}\),以及一个整数 \(X\) 和初始位置 \(P,Q\)。
初始时,高桥位于 \((P,Q)\) 单元格中,强度为 \(S _ {P,Q}\)。
现在高桥可以上下左右移动,每次移动后,高桥会吸收与其相邻的粘液中强度严格小于高桥强度的 \(\dfrac{1}{X}\) 倍的粘液,并将其吸收,高桥的强度会增加被吸收粘液的强度。
问高桥最终的强度是多少。
解题思路
很显然,我们肯定选择强度最小的粘液吸收,由于粘液会随着高桥的移动越来越多,可以用一个优先队列来维护当前可接触到的粘液的最小值,然后贪心选粘液强度最小值即可。
注意\(S_{i,j} X\)会超出long long
范围,需要用__int128
来存储。
神奇的代码
#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, x, p, q;
cin >> h >> w >> x >> p >> q;
vector<vector<LL>> a(h, vector<LL>(w));
for (auto& i : a)
for (auto& j : i)
cin >> j;
priority_queue<array<LL, 3>, vector<array<LL, 3>>, greater<array<LL, 3>>>
pq;
array<int, 4> dx = {1, 0, -1, 0};
array<int, 4> dy = {0, 1, 0, -1};
--p, --q;
for (int i = 0; i < 4; i++) {
int nx = p + dx[i];
int ny = q + dy[i];
if (nx < 0 || nx >= h || ny < 0 || ny >= w)
continue;
pq.push({a[nx][ny], nx, ny});
a[nx][ny] = -1;
}
LL cur = a[p][q];
a[p][q] = -1;
while (!pq.empty()) {
auto [s, u, v] = pq.top();
if (__int128(s) * x >= cur) {
break;
}
pq.pop();
cur += s;
for (int i = 0; i < 4; i++) {
int nx = u + dx[i];
int ny = v + dy[i];
if (nx < 0 || nx >= h || ny < 0 || ny >= w || a[nx][ny] == -1)
continue;
pq.push({a[nx][ny], nx, ny});
a[nx][ny] = -1;
}
}
cout << cur << '\n';
return 0;
}
F - Double Sum 2 (abc384 F)
题目大意
定义 \(f(x)\) 表示将 \(2\) 的幂因子全部去掉后的数。
给定长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\) ,求 \(\displaystyle \sum_{i=1}^N \sum_{j=i}^N f(A_i+A_j)\) 。
解题思路
建议看第二个做法,该做法是赛时的做法,分情况讨论复杂了,写题解时发现第二类情况的做法具有通用型。
首先我们可以将 \(A_i\) 分解为 \(2^k \times t\) ,其中 \(t\) 为奇数,然后对\(k\)进行排序。
然后考虑两个数\(A_i, A_j(i < j)\),根据其 \(k\) 是否相同,可以分为两种情况:
- \(k_i \neq k_j\),即 \(k_i < k_j\),此时 \(f(A_i+A_j)=f(2^{k_i}t_i+2^{k_j}t_j)=f(2^{k_i}(t_i + 2^{k_j - k_i}t_j)) = t_i + 2^{k_j - k_i}t_j = \frac{A_i + A_j}{2^{k_i}}\),因为
奇+偶=奇
。所以我们可以枚举 \(i\),考虑所有的 \(j\)的贡献,设其数量为 \(cnt\) ,对应的 \(A_j\)的和为 \(sum\) ,那么 \(\sum_j f(A_i+A_j) = \frac{sum + cnt \times A_i}{2^{k_i}}\)。 - \(k_i = k_j\),此时 \(f(A_i+A_j)=f(2^{k_i}t_i+2^{k_j}t_j)=f(2^{k_i}(t_i + t_j)) = f(t_i + t_j)\),由于
奇+奇=偶
,所以\(t_i + t_j\)仍要除以\(2\)的幂,但关键是幂是多少呢?诚然,我们可以直接加然后求幂,但这样复杂度必然会变成\(O(n^2)\)。不能这样做。
注意到\(A_i \leq 1e7\),因此幂的可能情况不超过\(25\)种,我们可以枚举幂,然后统计每个幂\(k\),有多少的\(A_i + A_j\)是\(2^k\)的倍数。注意这个的统计和\(D题\)做法一样:如果 \(A_i + A_j\) 是 \(2^k\) 的倍数,就意味着 \(A_i \% 2^k + A_j \% 2^k = 0 \mod 2^k\),即 \(A_i \% 2^k = 2^k - A_j \% 2^k\),我们可以枚举\(A_i\),用set
统计符合条件的\(A_j\)的数量即可,通过维护对应取模值为\(x\)的\(A_i\)的和及数量,就能得到满足\(A_i + A_j = 0 \mod 2^k\)的\(\sum\sum A_i + A_j\),该幂其对答案的贡献就是\(\frac{\sum\sum A_i + A_j}{2^k}\)。
但这并不是正确的,考虑\(\frac{A_i + A_j}{2^k}\)才是奇数,再上述计数下,最终的和包括了 \(\frac{A_i + A_j}{2^k} + \frac{A_i + A_j}{2^{k-1}} + \cdots + \frac{A_i + A_j}{2^1}\),实际上只有\(\frac{A_i + A_j}{2^k}\)对答案才有贡献(它才是\(f(A_i + A_j)\)的值),其余都不是,我们得剔除其余部分。
怎么剔除呢?注意到是等比数列,设\(S_k = \sum_i\sum_j \frac{A_i + A_j}{2^k} + \frac{A_i + A_j}{2^{k-1}} + \cdots + \frac{A_i + A_j}{2^1}\),则\(2S = \sum_i\sum_j\frac{A_i + A_j}{2^k-1} + \frac{A_i + A_j}{2^{k-1}} + \cdots + A_i + A_j\),则\(2S_k - S_k = S_k = \sum_i\sum_j A_i + A_j - \frac{A_i + A_j}{2^k-1}\)
所以\(\frac{A_i + A_j}{2^k-1} = \sum_i \sum_j A_i + A_j - S_k\),左右两边对 \(k\)求和,就是\(ans = \sum_i\sum_j A_i + A_j - \sum_k S_k\)。第一项就是所有\(A_i\)的俩俩相加,第二项就是我们通过上述方法求得和。这样就得到了答案。
神奇的代码
#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> a(n);
for (auto& i : a)
cin >> i;
vector<int> c2(n);
auto calc = [](int x) {
int ret = 0;
while (~x & 1) {
x >>= 1;
++ret;
}
return ret;
};
int up = 25;
vector<int> p2(up);
p2[0] = 1;
for (auto i = 1; i < up; ++i) {
p2[i] = p2[i - 1] << 1;
}
LL ans = 0;
for (auto i = 0; i < n; ++i) {
c2[i] = calc(a[i]);
ans += a[i] / p2[c2[i]];
}
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int x, int y) { return c2[x] < c2[y]; });
LL tot = accumulate(a.begin(), a.end(), 0ll);
int cnt = n;
auto solve = [&](auto l, auto r) {
LL ret = 0;
for (int i = up - 1; i >= 1; --i) {
map<int, LL> sum, cnt;
for (auto j = l; j != r; j = next(j)) {
int mod = a[*j] % p2[i];
int other = mod ? (p2[i] - mod) : 0;
ret += (sum[other] + 1ll * cnt[other] * a[*j]) / p2[i];
sum[mod] += a[*j];
cnt[mod] += 1;
}
}
LL all = 0;
LL pre = 0;
for (auto j = l; j != r; j = next(j)) {
all += pre + 1ll * a[*j] * (j - l);
pre += a[*j];
}
ret = all - ret;
return ret;
};
for (auto i = id.begin(); i != id.end();) {
LL ss = a[*i];
LL cnt2 = 1;
tot -= a[*i];
cnt -= 1;
auto nxt = next(i);
while (nxt != id.end() && c2[*nxt] == c2[*i]) {
tot -= a[*nxt];
cnt -= 1;
ss += a[*nxt];
cnt2 += 1;
nxt = next(nxt);
}
ans += 1ll * cnt2 * tot / p2[c2[*i]] + 1ll * cnt * (ss) / p2[c2[*i]];
ans += solve(i, nxt);
i = nxt;
}
cout << ans << '\n';
return 0;
}
写到这里忽然发现这种做法也能覆盖\(k_i \neq k_j\)的情况。即\(\sum_i\sum_j f(A_i + A_j) = \sum_i\sum_j\frac{A_i + A_j}{2^k}\),直接枚举\(i,j\)必然是 \(O(n^2)\),所以我们转而枚举\(k\),即\(\sum_k \frac{1}{2^k} \sum_i\sum_j{A_i + A_j}[A_i + A_j = 0 \mod 2^k, A_i + A_j \neq 0 \mod 2^{k+1}]\),然后设法统计这样的\(A_i + A_j\)的和,对所有的\(k\)相加,即为答案。
怎么统计呢?统计\(A_i + A_j = 0 \mod 2^k\)的\(A_i + A_j\)和的方法形同D题做法,即枚举\(A_i\),然后用map
维护其对应取模的值和数量,但上述要求不包含\(0 \mod 2^{k+1}\)的情况,那就直接从\(0 \mod 2^k\)减去\(0 \mod 2^{k+1}\)即可,这样就能得到\(0 \mod 2^k\)但不是\(0 \mod 2^{k+1}\)的情况。
得用unordered_map
来维护,map
可能会超时。
神奇的代码
#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> a(n);
for (auto& i : a)
cin >> i;
constexpr int up = 26;
vector<int> p2(up);
p2[0] = 1;
for (auto i = 1; i < up; ++i) {
p2[i] = p2[i - 1] << 1;
}
LL ans = 0;
array<unordered_map<int, LL>, up> sum, cnt;
for (auto j = 0; j < n; j++) {
for (int i = up - 2; i >= 0; --i) {
int mod = a[j] % p2[i];
int other = mod ? (p2[i] - mod) : 0;
int mod2 = a[j] % p2[i + 1];
int other2 = mod2 ? (p2[i + 1] - mod2) : 0;
sum[i][mod] += a[j];
cnt[i][mod] += 1;
ans += (sum[i][other] - sum[i + 1][other2] +
(cnt[i][other] - cnt[i + 1][other2]) * a[j]) /
p2[i];
}
}
cout << ans << '\n';
return 0;
}
G - Abs Sum (abc384 G)
题目大意
给定长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\) 和 \(B=(B_1,B_2,\ldots,B_N)\) 以及长度为 \(K\) 的整数序列 \(X=(X_1,X_2,\ldots,X_K)\) 和 \(Y=(Y_1,Y_2,\ldots,Y_K)\) 。
求每个 \(k=1,2,\ldots,K\) 的长度 \(\displaystyle \sum_{i=1}^{X_k} \sum_{j=1}^{Y_k} |A_i-B_j|\) 。
解题思路
<++>
神奇的代码