AtCoder Beginner Contest 384

省流版
  • A. 模拟转换即可
  • B. 模拟判断即可
  • C. 枚举所有情况排序即可
  • D. 分区间和与跨区间和,后者为一个前后缀+区间和,分别判断即可
  • E. 贪心选强度最小的粘液,优先队列维护即可
  • F. 考虑除以2k,枚举k,统计满足Ai+Aj=0mod2kAi+Aj0mod2k+1Ai+Bi的和,即做两次D题的做法即可。

A - aaaadaa (abc384 A)

题目大意

给定一个字符串S和两个字符c1c2,将所有非 c1 替换为 c2

解题思路

依次枚举每个字符判断即可。

神奇的代码
_, 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场比赛的ratinga,求最终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)

题目大意

给定五道题的分数,对于一个字符串,其分数为字符对应题目的分数之和。

共有 251=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=(A1,A2,A3,)

判断是否存在一个和为 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×W 的网格,每个单元格中有一个整数 Si,j,以及一个整数 X 和初始位置 P,Q

初始时,高桥位于 (P,Q) 单元格中,强度为 SP,Q

现在高桥可以上下左右移动,每次移动后,高桥会吸收与其相邻的粘液中强度严格小于高桥强度的 1X 倍的粘液,并将其吸收,高桥的强度会增加被吸收粘液的强度。

问高桥最终的强度是多少。

解题思路

很显然,我们肯定选择强度最小的粘液吸收,由于粘液会随着高桥的移动越来越多,可以用一个优先队列来维护当前可接触到的粘液的最小值,然后贪心选粘液强度最小值即可。

注意Si,jX会超出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=(A1,A2,,AN) ,求 i=1Nj=iNf(Ai+Aj)

解题思路

建议看第二个做法,该做法是赛时的做法,分情况讨论复杂了,写题解时发现第二类情况的做法具有通用型。

首先我们可以将 Ai 分解为 2k×t ,其中 t 为奇数,然后对k进行排序。

然后考虑两个数Ai,Aj(i<j),根据其 k 是否相同,可以分为两种情况:

  • kikj,即 ki<kj,此时 f(Ai+Aj)=f(2kiti+2kjtj)=f(2ki(ti+2kjkitj))=ti+2kjkitj=Ai+Aj2ki,因为奇+偶=奇。所以我们可以枚举 i,考虑所有的 j的贡献,设其数量为 cnt ,对应的 Aj的和为 sum ,那么 jf(Ai+Aj)=sum+cnt×Ai2ki
  • ki=kj,此时 f(Ai+Aj)=f(2kiti+2kjtj)=f(2ki(ti+tj))=f(ti+tj),由于奇+奇=偶,所以ti+tj仍要除以2的幂,但关键是幂是多少呢?诚然,我们可以直接加然后求幂,但这样复杂度必然会变成O(n2)。不能这样做。

注意到Ai1e7,因此幂的可能情况不超过25种,我们可以枚举幂,然后统计每个幂k,有多少的Ai+Aj2k的倍数。注意这个的统计和D做法一样:如果 Ai+Aj2k 的倍数,就意味着 Ai%2k+Aj%2k=0mod2k,即 Ai%2k=2kAj%2k,我们可以枚举Ai,用set统计符合条件的Aj的数量即可,通过维护对应取模值为xAi的和及数量,就能得到满足Ai+Aj=0mod2kAi+Aj,该幂其对答案的贡献就是Ai+Aj2k

但这并不是正确的,考虑Ai+Aj2k才是奇数,再上述计数下,最终的和包括了 Ai+Aj2k+Ai+Aj2k1++Ai+Aj21,实际上只有Ai+Aj2k对答案才有贡献(它才是f(Ai+Aj)的值),其余都不是,我们得剔除其余部分。

怎么剔除呢?注意到是等比数列,设Sk=ijAi+Aj2k+Ai+Aj2k1++Ai+Aj21,则2S=ijAi+Aj2k1+Ai+Aj2k1++Ai+Aj,则2SkSk=Sk=ijAi+AjAi+Aj2k1

所以Ai+Aj2k1=ijAi+AjSk,左右两边对 k求和,就是ans=ijAi+AjkSk。第一项就是所有Ai的俩俩相加,第二项就是我们通过上述方法求得和。这样就得到了答案。

神奇的代码
#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;
}


写到这里忽然发现这种做法也能覆盖kikj的情况。即ijf(Ai+Aj)=ijAi+Aj2k,直接枚举i,j必然是 O(n2),所以我们转而枚举k,即k12kijAi+Aj[Ai+Aj=0mod2k,Ai+Aj0mod2k+1],然后设法统计这样的Ai+Aj的和,对所有的k相加,即为答案。

怎么统计呢?统计Ai+Aj=0mod2kAi+Aj和的方法形同D题做法,即枚举Ai,然后用map维护其对应取模的值和数量,但上述要求不包含0mod2k+1的情况,那就直接从0mod2k减去0mod2k+1即可,这样就能得到0mod2k但不是0mod2k+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=(A1,A2,,AN)B=(B1,B2,,BN) 以及长度为 K 的整数序列 X=(X1,X2,,XK)Y=(Y1,Y2,,YK)

求每个 k=1,2,,K 的长度 i=1Xkj=1Yk|AiBj|

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(274)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2019-12-14 Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)
点击右上角即可分享
微信分享提示