Atcoder ABC 346 全题解

闲话

  1. 上一篇全题解反响不错,如果大家支持我就会继续更。

  2. 我 ABC 也打了,ARC 也打了,没打好,疯狂掉大分……包括本场比赛也是整整补了 EFG 三道题,以及 ARC 死磕 D 结果使赛后五分钟 AC 又有素材了……

A

懒得讲

B

由于我被 B 题坑了,所以在此纪念。

最简单的方法就是把字符串复制 N 遍,然后暴力找。

N 太小会 WA,太大会 TLE,我选的 1024 遍,AC 了。

// Problem: B - Piano
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_b
// 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); }

// Your code goes here...

string s = "wbwbwwbwbwbw";

int main() {
	int a, b;
	scanf("%d %d", &a, &b);
	for (int i = 0; i < 10; i++) { // 注:2^10=1024
		s = s + s;
	}
	for (int i = 0; i < s.size(); i++) {
		int ca = 0, cb = 0;
		for (int j = 0; j < a + b; j++) {
			ca += (s[i + j] == 'w');
			cb += (s[i + j] == 'b');
		}
		if (ca == a && cb == b) {
			puts("Yes");
			return 0;
		}
	}
	puts("No");
}

C

我们可以用 1K 的和减去其中出现在数组里的数的和。

每遍历到一个数:

  • 如果它出现过或者 >K,那就不理它。

  • 否则,就把这个数从和里剪掉。

至于怎么看它出没出现过,用 set 就可以了。

// Problem: C - Σ
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_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); }

// Your code goes here...

unordered_set<int> st;

int main() {
	int n;
	ll k, sum;
	scanf("%d %lld", &n, &k);
	sum = k * (k + 1) / 2;
	for (int i = 0; i < n; i++) {
		ll x;
		scanf("%lld", &x);
		if (x <= k && !st.count(x)) {
			st.insert(x);
			sum -= x;
		}
	}
	printf("%lld", sum);
}

D

官方说有两种方法,一种是直接从头 DP,一种是从两端 DP,我是从头 DP 的。

f(i,j,k) 表示目前到第 i 位,上一个是 j,已经出现了 k 对相邻的 00 或 11。

为了表述方便,设 g(i,j) 为把第 i 位变成 j 所需的代价。

则:

{f(i,0,0)=f(i1,1,0)+g(i,0)f(i,1,0)=f(i1,0,0)+g(i,1)f(i,0,1)=min(f(i1,1,1),f(i1,0,0))+g(i,0)f(i,1,1)=min(f(i1,0,1),f(i1,1,1))+g(i,1)

最终答案显然就是 min(f(n,0,1),f(n,1,1)))

// Problem: D - Gomamayo Sequence
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_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); }

// Your code goes here...

ll f[200005][2][2];
ll a[200005];

int main() {
	int n;
	string s;
	cin >> n;
	cin >> s;
	for (int i = 0; i < n; i++) {
		scanf("%lld", &a[i]);
	}
	memset(f, 0x3f, sizeof f);
	f[0][0][0] = (s[0] != '0') * a[0];
	f[0][1][0] = (s[0] != '1') * a[0];
	for (int i = 1; i < n; i++) {
		f[i][0][0] = f[i - 1][1][0] + (s[i] != '0') * a[i];
		f[i][1][0] = f[i - 1][0][0] + (s[i] != '1') * a[i];
		f[i][0][1] = min(f[i - 1][0][0], f[i - 1][1][1]) + (s[i] != '0') * a[i];
		f[i][1][1] = min(f[i - 1][1][0], f[i - 1][0][1]) + (s[i] != '1') * a[i];
	}
	printf("%lld", min(f[n - 1][0][1], f[n - 1][1][1]));
}

E

老正难则反了。

假如我们从后往前,那么操作就变成了:

  • 把某一行(列)上所有还未确定颜色的格子确定颜色 X

当然了,最后还得加一个:

  • 把所有还未确定颜色的格子确定颜色 0

这样就好办了。

当我们对行操作的时候,就把总列数减去操作过的列数,即为当前操作确定的格子数。对列同理。

注意一下我们操作一行(列)第二次时不会有任何效果,因此还得额外开一个数组记录每行(列)有没有被操作过。

// Problem: E - Paint
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_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); }

// Your code goes here...

struct op {
	int t, a, x;
} a[200005];

bool hvis[200005], wvis[200005];

map<int, ll> mp;

int main() {
	int h, w, n;
	scanf("%d %d %d", &h, &w, &n);
	for (int i = 0; i < n; i++) {
		scanf("%d %d %d", &a[i].t, &a[i].a, &a[i].x);
		a[i].a--;
	}
	ll hcnt = h, wcnt = w;
	for (int i = n - 1; i >= 0; i--) {
		if (a[i].t == 1) {
			if (!hvis[a[i].a]) {
				hvis[a[i].a] = 1;
				hcnt--;
				mp[a[i].x] += wcnt;
			}
		} else {
			if (!wvis[a[i].a]) {
				wvis[a[i].a] = 1;
				wcnt--;
				mp[a[i].x] += hcnt;
			}
		}
	}
	mp[0] += hcnt * wcnt;
	int ans = mp.size();
	for (auto xzcyyds : mp) {
		if (!xzcyyds.second) {
			ans--;
		}
	}
	printf("%d\n", ans);
	for (auto xzcyyds : mp) {
		if (xzcyyds.second) {
			printf("%d %lld\n", xzcyyds.first, xzcyyds.second);
		}
	}
}

F

显然,假如 g(T,k)f(S,N) 的子串,那么 g(T,k1) 也是 f(S,N) 的子串,因此我们可以二分。

对于 T 的每一个字符,我们可以分开处理,维护每一个字符后会匹配到 f(S,N) 的哪里。(应该)有两种方式:

  1. S 的前缀后缀某个字符的个数都统计出来,然后把 k 个字符分成三段:一段后缀、重复循环,一段前缀。巨难写。

  2. 倍增。没写过,不知道好不好写。

假如你 WA 了,可以试试这组数据:

2
aa
aa
// Problem: F - SSttrriinngg in StringString
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_f
// Memory Limit: 1024 MB
// Time Limit: 3000 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); }

// Your code goes here...

#define ll long long

ll lim;
int n, m;
string s, t;
ll pre[30][100005];
ll suf[30][100005];

bool check(ll k) {
	// cerr << k << endl;
	ll rep = 0;
	int idx = 0;
	for (int i = 0; i < m; i++) {
		int x = t[i] - 'a';
		if (!suf[x][0]) {
			return 0;
		}
		if (suf[x][idx] >= k) { // 如果一个 S 可以解决问题
			idx = lower_bound(pre[x], pre[x] + n, (idx ? pre[x][idx - 1] : 0) + k) - pre[x] + 1;
		} else { // 否则
			ll cnt = k - suf[x][idx]; // 后缀
			// cerr << "#" << suf[0][0] << endl;
			rep++;
			idx = 0;
			rep += (cnt - 1) / suf[x][0]; // 循环
			cnt %= suf[x][0];
			if (!cnt) {
				cnt += suf[x][0];
			}
			idx = lower_bound(pre[x], pre[x] + n, cnt) - pre[x] + 1; // 前缀
		}
		// cerr << t[i] << " " << rep << " " << idx << endl;
		if (rep >= lim && (rep != lim || idx)) {
			// cerr << endl;
			return 0;
		}
	}
	// cerr << endl;
	return rep < lim || (rep == lim && !idx);
}

int main() {
	cin >> lim >> s >> t;
	n = s.size(), m = t.size();
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < 26; j++) {
			if (s[i] == 'a' + j) {
				pre[j][i] = (i ? pre[j][i - 1] : 0) + 1;
			} else {
				pre[j][i] = (i ? pre[j][i - 1] : 0);
			}
		}
	}
	for (int i = n - 1; i >= 0; i--) {
		for (int j = 0; j < 26; j++) {
			if (s[i] == 'a' + j) {
				suf[j][i] = suf[j][i + 1] + 1;
			} else {
				suf[j][i] = suf[j][i + 1];
			}
		}
	}
	ll l = 0, r = lim * (s.size()) / (t.size());
	// 因为这个上界我 WA 了好多次,选这个就对了
	while (l < r) { // 二分
		ll mid = l + ((r - l) >> 1) + 1;
		if (check(mid)) {
			l = mid;
		} else {
			r = mid - 1;
		}
	}
	printf("%lld", l);
}

G

闲话:我在补这题时在题解里看到了 Lazy Segment Tree,然而直到我补掉这题我还是不知道这是个什么东东。

首先,可以发现,对于每一个数 Ai,可以找到它左边跟它相同的数和右边跟它相同的数,设它们的位置为 l,r,则 (l+1i,ir1) 都是符合条件的。

考虑维护一个二维数组 f,其中 f(i,j) 代表 (i,j) 符不符合条件。那么,对于上面提到的 l,i,r,就相当于把 f(l+1,i)f(i,r1) 给置成 1就相当于是放置一个矩形,答案就是矩形的面积并。

因此用扫描线搞搞就结束了。

注:数组大小要开的大一点,不然会蜜汁 RE。

// Problem: G - Alone
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// URL: https://atcoder.jp/contests/abc346/tasks/abc346_g
// Memory Limit: 1024 MB
// Time Limit: 3000 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); }

// Your code goes here...

struct node {
	ll cover, ans;
	node() {
		cover = ans = 0;
	}
} tree[3200005];

// 扫描线专用的线段树
// 关于扫描线的知识请自行 bdfs

void reload(int l, int r, int p) {
	if (tree[p].cover > 0) {
		tree[p].ans = (r - l + 1);
	} else {
		tree[p].ans = (tree[p * 2].ans + tree[p * 2 + 1].ans);
	}
}

void update(int L, int R, int x, int l, int r, int p) {
	if (L <= l && r <= R) {
		tree[p].cover += x;
		reload(l, r, p);
	} else {
		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);
		}
		reload(l, r, p);
	}
}

ll getans() {
	return tree[1].ans;
}

vector<int> idx[400005]; // 为了找 l,r 用的 idx 数组
int a[400005];

struct edge {
	int l, r, val;
	edge(int _l = 114, int _r = 514, int v = 0) {
		l = _l, r = _r, val = v;
	}
};

vector<edge> change[400005];

void add_rect(int r1, int c1, int r2, int c2) {
	change[r1].push_back(edge(c1, c2, 1));
	change[r2 + 1].push_back(edge(c1, c2, -1));
}

int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= 400000; i++) {
		idx[i].push_back(0);
	}
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		idx[a[i]].push_back(i);
	}
	for (int i = 1; i <= 400000; i++) {
		idx[i].push_back(n + 1);
	}
	for (int i = 1; i <= n; i++) {
		int pos = lower_bound(idx[a[i]].begin(), idx[a[i]].end(), i) - idx[a[i]].begin();
		// 这里的 l,r 事实上是题目中的 l + 1 和 r - 1
		// 直接二分,为了避免越界在 idx 里面各加一个 0 和一个 n + 1
		// 这样左边没有时 l = 1,代表 L 从 1 开始到 i 都可以
		// r 同理
		int l = idx[a[i]][pos - 1] + 1;
		int r = idx[a[i]][pos + 1] - 1;
		add_rect(l, i, i, r);
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		for (edge x : change[i]) {
			update(x.l, x.r, x.val, 1, n, 1);
		}
		ans += getans();
	}
	printf("%lld", ans);
}
posted @   A-Problem-Solver  阅读(296)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示