Atcoder ABC 342 全题解

闲话

当我还是一个只会 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 = p_1^{2e_1} \times p_2^{2e_2} \times \cdots \times p_k^{2e_k} \]

那么对于 $ 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 了。

image


好的那么正向很难搞,那我们能不能时光倒流呢?

答案是可以的。然后改改 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));
		}
	}
}
posted @ 2024-02-25 12:09  A-Problem-Solver  阅读(500)  评论(0编辑  收藏  举报