闲话
当我还是一个只会 AB 的小蒟蒻时,由于不会 C 又看不懂官方题解,只好看网上的题解。
结果:
ABC
签到题不讲
AB
对着题意模拟即可。
A 有个好玩的做法,先看前两个,如果不同跟第三个比较,如果相同看后面哪个字母跟第一个不一样。
C
由于是将所有的 $ c_i $ 替换,所以可得同一个字母最后替换成的字母都一样。
于是可以考虑 abcdefghijklmnopqrstuvwxyz
最后会变成什么。时间复杂度 $ O(26Q + N) $。
// Problem: C - Many Replacement
// Contest: AtCoder - HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
// URL: https://atcoder.jp/contests/abc342/tasks/abc342_c
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#ifdef LOCAL
#include "debugger.h"
#define debug(x) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::print(x), cerr << endl)
#define debug_arr(x, n) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::printarr(x, n), cerr << endl)
#else
#include <bits/stdc++.h>
#define debug(x) 114514
#define debug_arr(x, n) 1919810
#endif
using namespace std;
#define ll long long
ll max(int a, ll b) { return max((ll)a, b); }
ll min(int a, ll b) { return min((ll)a, b); }
ll max(ll a, int b) { return max(a, (ll)b); }
ll min(ll a, int b) { return min(a, (ll)b); }
int max(int a, size_t b) { return max(a, (int)b); }
int min(int a, size_t b) { return min(a, (int)b); }
int max(size_t a, int b) { return max((int)a, b); }
int min(size_t a, int b) { return min((int)a, b); }
// Your code goes here...
char to[128];
int main() {
int n;
cin >> n;
string s;
cin >> s;
for (char i = 'a'; i <= 'z'; i++) {
to[i] = i;
}
int q;
scanf("%d", &q);
while (q--) {
char x, y;
cin >> x >> y;
for (char i = 'a'; i <= 'z'; i++) {
if (to[i] == x) {
to[i] = y;
}
}
}
for (int i = 0; i < n; i++) {
putchar(to[s[i]]);
}
}
D
首先是一个重要的性质,平方数的只质因数分解:
那么对于 $ x^2 = ab $ 里面每个质因子,要么 $ a $ 和 $ b $ 里面都有奇数个,要么都有偶数个。
于是可以将 $ a, b $ 都除以最大能整除的平方,然后开个桶计数就可以了。
// Problem: D - Square Pair
// Contest: AtCoder - HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
// URL: https://atcoder.jp/contests/abc342/tasks/abc342_d
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#ifdef LOCAL
#include "debugger.h"
#define debug(x) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::print(x), cerr << endl)
#define debug_arr(x, n) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::printarr(x, n), cerr << endl)
#else
#include <bits/stdc++.h>
#define debug(x) 114514
#define debug_arr(x, n) 1919810
#endif
using namespace std;
#define ll long long
ll max(int a, ll b) { return max((ll)a, b); }
ll min(int a, ll b) { return min((ll)a, b); }
ll max(ll a, int b) { return max(a, (ll)b); }
ll min(ll a, int b) { return min(a, (ll)b); }
int max(int a, size_t b) { return max(a, (int)b); }
int min(int a, size_t b) { return min(a, (int)b); }
int max(size_t a, int b) { return max((int)a, b); }
int min(size_t a, int b) { return min((int)a, b); }
// Your code goes here...
int a[200005], cnt[200005];
int main() {
int m;
scanf("%d", &m);
ll ans = 0;
int n = 0, zero = 0;
for (int i = 0; i < m; i++) {
int x;
scanf("%d", &x);
if (x == 0) {
ans += m - 1;
zero++;
} else {
a[n++] = x;
}
}
ans -= 1ll * zero * (zero - 1) / 2;
for (int i = 0; i < n; i++) {
for (int j = sqrt(a[i]); j >= 1; j--) {
if (a[i] % (j * j) == 0) {
a[i] /= (j * j);
}
}
}
for (int i = 0; i < n; i++) {
ans += cnt[a[i]];
cnt[a[i]]++;
}
printf("%lld", ans);
}
E
这场我先做的 F 再做的 E,幸好我 F 写的是树状数组,不然我就没时间调 E 了。
好的那么正向很难搞,那我们能不能时光倒流呢?
答案是可以的。然后改改 dij 就可以了。
具体做法就是建反图,经过一条边时考虑能赶上的最晚的火车(时光倒流了所以选最晚的),没有就当这条边不存在,否则就用这班火车过去。
// Problem: E - Last Train
// Contest: AtCoder - HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
// URL: https://atcoder.jp/contests/abc342/tasks/abc342_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#ifdef LOCAL
#include "debugger.h"
#define debug(x) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::print(x), cerr << endl)
#define debug_arr(x, n) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::printarr(x, n), cerr << endl)
#else
#include <bits/stdc++.h>
#define debug(x) 114514
#define debug_arr(x, n) 1919810
#endif
using namespace std;
#define ll long long
ll max(int a, ll b) { return max((ll)a, b); }
ll min(int a, ll b) { return min((ll)a, b); }
ll max(ll a, int b) { return max(a, (ll)b); }
ll min(ll a, int b) { return min(a, (ll)b); }
int max(int a, size_t b) { return max(a, (int)b); }
int min(int a, size_t b) { return min(a, (int)b); }
int max(size_t a, int b) { return max((int)a, b); }
int min(size_t a, int b) { return min((int)a, b); }
// Your code goes here...
struct train {
ll l, d, k, c;
int v;
train(ll _l, ll _d, ll _k, ll _c, int _v) {
l = _l, d = _d, k = _k, c = _c, v = _v;
}
};
vector<train> graph[200005];
ll f[200005];
priority_queue<pair<ll, int> > que;
int main() {
int n, m;
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++) {
ll l, d, k, c;
int a, b;
scanf("%lld %lld %lld %lld %d %d", &l, &d, &k, &c, &a, &b);
graph[b].push_back(train(l + (k - 1) * d + c, d, k, c, a));
}
memset(f, -1, sizeof f);
f[n] = 1e18 + 1e9;
que.emplace(1e18 + 1e9, n);
while (que.size()) {
ll now = que.top().first;
int u = que.top().second;
que.pop();
if (f[u] < now) {
continue;
}
for (auto eg : graph[u]) {
int v = eg.v;
ll tim = now / eg.d * eg.d + eg.l % eg.d;
if (tim > now) {
tim -= eg.d;
}
tim = min(tim, eg.l);
if (tim >= eg.l - (eg.k - 1) * eg.d && tim - eg.c > f[v]) {
f[v] = tim - eg.c;
que.emplace(f[v], v);
}
}
}
for (int i = 1; i < n; i++) {
if (f[i] == -1) {
puts("Unreachable");
} else {
printf("%lld\n", f[i]);
}
}
}
F
据说 Black Jack 是扑克牌 21 点加上一些奇奇怪怪的规则,这也解释了为什么 $ x > N $ 你就输了(笑)
称你的敌人为庄家。
首先,由于庄家的策略固定,所以你可以先处理出庄家停在每一个点上的概率。有了这个概率,你就可以用前缀和 $ O(1) $ 计算当 $ x = i $ 时你赢的概率,把这个算你赢的概率的函数叫做 $ \operatorname{solve} $。
接下来考虑你赢的概率,设 $ f_i $ 为假如你当前点数为 $ i $,在最优策略下你赢的概率。
假如你扔骰子,那么赢的概率就是 $ \cfrac{\sum_{k=1}^{D} f_{i+k}}{D} $。
假如你不扔,那么赢的概率就是 $ g(i) $。
所以可得出状态转移方程(注意需要倒着转移):
\[f_i = \max(\cfrac{\sum_{k=1}^{D} f_{i+k}}{D}, \operatorname{solve}(i)) \]最终的答案就是 $ f_0 $。
那么庄家的点数概率该怎么算呢?
设这个数组为 $ g $。
接下来,将 $ i $ 从 $ 1 $ 循环到 $ L - 1 $,将 $ g_i $ 的概率平均分配到 $ g_{i + 1} \sim g_{i + D} $。
然后就可以了。别问我为什么?
问题来了,显然我们不能 $ O(LD) $ 搞这个,那我们怎么做呢?
其实这个就是区间修改单点查询,差分版树状数组维护一下就可以了。
最后一个小问题:$ \operatorname{solve} $ 怎么求呢?
很简单,考虑是 $ y > N $ 赢的还是 $ x > y $ 赢的,将 $ g $ 数组前缀和就可以了。
友情提醒:数组要开到 $ 400000 $。
// Problem: F - Black Jack
// Contest: AtCoder - HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
// URL: https://atcoder.jp/contests/abc342/tasks/abc342_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#ifdef LOCAL
#include "debugger.h"
#define debug(x) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::print(x), cerr << endl)
#define debug_arr(x, n) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::printarr(x, n), cerr << endl)
#else
#include <bits/stdc++.h>
#define debug(x) 114514
#define debug_arr(x, n) 1919810
#endif
using namespace std;
#define ll long long
ll max(int a, ll b) { return max((ll)a, b); }
ll min(int a, ll b) { return min((ll)a, b); }
ll max(ll a, int b) { return max(a, (ll)b); }
ll min(ll a, int b) { return min(a, (ll)b); }
int max(int a, size_t b) { return max(a, (int)b); }
int min(int a, size_t b) { return min(a, (int)b); }
int max(size_t a, int b) { return max((int)a, b); }
int min(size_t a, int b) { return min((int)a, b); }
// Your code goes here...
namespace bit {
double c[400005];
void _update(int i, double x) {
i++;
while (i < 400003) {
c[i] += x;
i += (i & -i);
}
}
void update(int l, int r, double x) {
_update(l, x);
_update(r + 1, -x);
}
double query(int x) {
x++;
double ans = 0;
while (x) {
ans += c[x];
x -= (x & -x);
}
return ans;
}
};
int n, l, d;
double g[400005];
void calc_g() {
// g[i]: 庄家点数为 i 的概率,前缀和
bit::update(0, 0, 1);
for (int i = 0; i <= 400000; i++) {
g[i] = bit::query(i);
if (i < l) {
bit::update(i + 1, i + d, g[i] / d);
g[i] = 0;
}
}
for (int i = 1; i <= 400000; i++) {
g[i] += g[i - 1];
}
}
double f[400005];
double solve(int x) {
if (x > n) {
return 0;
} else {
double ans = 1 - g[n];
if (x > l) {
ans += g[x - 1];
}
return ans;
}
}
void calc_f() {
// f[i]: 你当前为 i,赢的概率
// solve(i): x = i 赢的概率
// f[i] = max((f[i + 1] + f[i + 2] + ... + f[i + d]) / d, solve(i))
double sum = 0;
for (int i = 400000; i >= 0; i--) {
if (i > n) {
f[i] = 0;
} else {
f[i] = max(sum / d, solve(i));
}
sum += f[i];
if (i + d <= 400000) {
sum -= f[i + d];
}
}
}
int main() {
scanf("%d %d %d", &n, &l, &d);
calc_g();
calc_f();
printf("%.15f", f[0]);
}
G
赛后补的。
假如没有 2 操作,那么可以用线段树,配合标记永久化实现。
那么 2 操作怎么搞呢?
这时候,我们就要用到一个高大上的东西:erasable_priority_queue!
???:说的这么好听,不就是 multiset 吗?
这个有什么用呢?
在更新的时候,我们不再是直接更新 $ \max $,而是插入一个 multiset,询问的时候取 multiset 最后一个元素。
这样,2 操作直接从 multiset 里删除就可以了。
好像也可以用分块。
// Problem: G - Retroactive Range Chmax
// Contest: AtCoder - HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
// URL: https://atcoder.jp/contests/abc342/tasks/abc342_g
// Memory Limit: 1024 MB
// Time Limit: 5000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#ifdef LOCAL
#include "debugger.h"
#define debug(x) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::print(x), cerr << endl)
#define debug_arr(x, n) (cerr << "LINE " << __LINE__ << ", " << #x << " = ", __HARVEY_DEBUG__::printarr(x, n), cerr << endl)
#else
#include <bits/stdc++.h>
#define debug(x) 114514
#define debug_arr(x, n) 1919810
#endif
using namespace std;
#define ll long long
ll max(int a, ll b) { return max((ll)a, b); }
ll min(int a, ll b) { return min((ll)a, b); }
ll max(ll a, int b) { return max(a, (ll)b); }
ll min(ll a, int b) { return min(a, (ll)b); }
int max(int a, size_t b) { return max(a, (int)b); }
int min(int a, size_t b) { return min(a, (int)b); }
int max(size_t a, int b) { return max((int)a, b); }
int min(size_t a, int b) { return min((int)a, b); }
// Your code goes here...
multiset<int> tree[800005];
void update(int L, int R, int x, int l, int r, int p) {
if (L <= l && r <= R) {
tree[p].insert(x);
return;
}
int mid = (l + r) / 2;
if (mid >= L) {
update(L, R, x, l, mid, p * 2);
}
if (mid < R) {
update(L, R, x, mid + 1, r, p * 2 + 1);
}
}
void cancel(int L, int R, int x, int l, int r, int p) {
if (L <= l && r <= R) {
tree[p].erase(tree[p].find(x));
return;
}
int mid = (l + r) / 2;
if (mid >= L) {
cancel(L, R, x, l, mid, p * 2);
}
if (mid < R) {
cancel(L, R, x, mid + 1, r, p * 2 + 1);
}
}
int query(int x, int l, int r, int p) {
int ans = (tree[p].size() ? (*tree[p].rbegin()) : 0);
if (l != r) {
int mid = (l + r) / 2;
if (x <= mid) {
ans = max(ans, query(x, l, mid, p * 2));
} else {
ans = max(ans, query(x, mid + 1, r, p * 2 + 1));
}
}
return ans;
}
struct qry {
int l = -1, r = -1, x = 0;
} a[200005];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
update(i, i, x, 1, n, 1);
}
int q;
scanf("%d", &q);
for (int i = 1; i <= q; i++) {
int op;
scanf("%d", &op);
if (op == 1) {
scanf("%d %d %d", &a[i].l, &a[i].r, &a[i].x);
update(a[i].l, a[i].r, a[i].x, 1, n, 1);
} else if (op == 2) {
int x;
scanf("%d", &x);
cancel(a[x].l, a[x].r, a[x].x, 1, n, 1);
} else {
int x;
scanf("%d", &x);
printf("%d\n", query(x, 1, n, 1));
}
}
}
标签:Atcoder,ABC,return,min,int,题解,ll,max,size
From: https://www.cnblogs.com/AProblemSolver/p/18032209