Codeforces Round 972 (Div. 2)

A

如果有两个相同的字符,中间隔了若干个字符,形如 A******A,那么会产生很多回文子序列。

为了避免这种情况,考虑将相同字符放在相邻位置,于是得到本题的正解构造:

首先将长度尽可能平分到 5 组,每组用相同字符重复 \(\lfloor\frac{n}{5}\rfloor\) 次或 \(\lceil\frac{n}{5}\rceil\) 次。

#include <bits/stdc++.h>
using namespace std;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

#define IOS
#define MULTI

char mp[] = { 'a', 'e', 'i', 'o', 'u' };

void solve() {
	int n;
	cin >> n;
	int m = n % 5, t = n / 5;
	for (int i = 0; i < m; ++i)
		for (int j = 0; j <= t; ++j)
			cout << mp[i];
	for (int i = m; i < 5; ++i)
		for (int j = 0; j < t; ++j)
			cout << mp[i];
	cout << '\n';
}

int main() {
#ifdef IOS
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
#endif
#ifdef MULTI
	int TestCase = 1;
	for (cin >> TestCase; TestCase--;) solve();
#else
	solve();
#endif
	return 0;
}

B

  1. David 被两个老师夹住:
    那么答案非常显然,假设两个老师之间有 \(k\) 个空位,David 只能活 \(\lceil\frac{k}{2}\rceil\) 轮。
    这个可以排序后用 lower_bound 找到 David 左右的老师。
  2. David 没有被老师夹住:
    那么 Daivd 显然会贪心地往边界跑,那么只需要求出离他最近的老师 \(x\) 的位置,答案就是 \(x - 1\) 或者 \(n - x - 1\)
#include <bits/stdc++.h>
using namespace std;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

#define IOS
#define MULTI

void solve() {
	int n, m, q;
	cin >> n >> m >> q;
	vector<int> b(m);
	for (int &x : b)
		cin >> x;
	sort(begin(b), end(b));
	for (; q--;) {
		int x;
		cin >> x;
		if (x < b[0]) {
			cout << b[0] - 1 << '\n';
		} else if (x > b.back()) {
			cout << n - b.back() << '\n';
		} else {
			int p = upper_bound(begin(b), end(b), x) - begin(b);
			int y = b[p] - b[p - 1] - 1;
			cout << (y + 1) / 2 << '\n';
		}
	}
}

int main() {
#ifdef IOS
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
#endif
#ifdef MULTI
	int TestCase = 1;
	for (cin >> TestCase; TestCase--;) solve();
#else
	solve();
#endif
	return 0;
}

C

给人一种显然 dp 的感觉。用 dp[i][j] 表示前 \(i\) 个字符串种选了若干个字符串,选完后 Narek 贪心匹配到了第 \(j~(0\le j<5)\) 个字符,此时的得分最大值。

那么只需要对每个字符串求出,假设之前已经匹配了 \(i~(0\le i<5)\) 个字符,经过这个字符串后,贪心匹配到了哪个字符(\(j\)),完美匹配了几次(\(k\))。用二元组 \(f(i) = (j, k)\) 记录即可转移。

#include <bits/stdc++.h>
using namespace std;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

#define IOS
#define MULTI

template<typename T>
void chmax(T &x, T y) {
	if (y > x)
		x = y;
}
int mp[256];

void solve() {
	int n, m;
	cin >> n >> m;
	vector<string> s(n);
	vector<int> cnt(n);
	vector<array<pair<int, int>, 5>> g(n);
	for (int i = 0; i < n; ++i) {
		cin >> s[i];
		array<pair<int, int>, 5> res;
		for (int j = 0; j < 5; ++j)
			res[j] = { 0, j };
		for (char ch : s[i]) {
			cnt[i] += !!mp[ch];
			for (int j = 0; j < 5; ++j) {
				auto &[ans, cur] = res[j];
				if (cur + 1 == mp[ch])
					++cur;
				if (cur == 5)
					cur = 0, ans += 5;
			}
		}
		g[i] = res;
		// cout << "string " << i << "\n";
		// for (int j = 0; j < 5; ++j) {
		// 	auto [a, c] = res[j];
		// 	cout << a << " " << c << '\n';
		// }
	}
	vector<array<int, 5>> f(n + 1);
	// f[i][j]: first i strings, matched to ch j, max
	for (int i = 0; i <= n; ++i)
		for (int j = 0; j < 5; ++j)
			f[i][j] = -1e9;
	f[0][0] = 0;
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < 5; ++j) {
			// cout << "f[" << i << "][" << j << "] = " << f[i][j] << '\n';
			auto [ans, cur] = g[i][j];
			chmax(f[i + 1][cur], f[i][j] + 2 * ans - cnt[i]);
			chmax(f[i + 1][j], f[i][j]);
		}
	}
	int ans = 0;
	for (int i = 0; i < 5; ++i)
		chmax(ans, f[n][i]);
	cout << ans << '\n';
}

int main() {
	mp['n'] = 1;
	mp['a'] = 2;
	mp['r'] = 3;
	mp['e'] = 4;
	mp['k'] = 5;
#ifdef IOS
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
#endif
#ifdef MULTI
	int TestCase = 1;
	for (cin >> TestCase; TestCase--;) solve();
#else
	solve();
#endif
	return 0;
}

D

需要利用一个非常常见的 gcd 的性质,就是前缀 gcd 不会有超过 \(\log V\) 种不同取值。

那么就可以暴力枚举了。先暴力枚举一个 \(l\),之后寻找所有有意义的 \(r\)

考虑什么样的 \(r\) 有意义:当且仅当 \(r_1\)\(r_2\)\(\gcd(a_{l\cdots r_1})\neq \gcd(a_{l\cdots r_2})\) 或者 \(\gcd(a_{r_1\cdots a_n}) \neq \gcd(a_{r_2\cdots n})\) 时这两个 \(r\) 的答案不同。显然第一个是前缀 \(\gcd\) 不同,第二个是后缀 \(\gcd\) 不同,那么最多有 \(2\log V\) 种本质不同的 \(r\)。从 \(r = l\) 开始二分查找即可,利用 st 表可以做到 \(O(\log V)\) 区间 \(\gcd\)。总时间复杂度 \(O(n \log n\log V)\)

#include <bits/stdc++.h>
using namespace std;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

#define IOS
#define MULTI

int gcd(int x, int y, int z) {
	return gcd(gcd(x, y), z);
}

void solve() {
	int n;
	cin >> n;
	vector<int> a(n), b(n), lg(n + 1);
	lg[0] = -1;
	for (int i = 1; i <= n; ++i)
		lg[i] = lg[i >> 1] + 1;
	for (auto &x : a)
		cin >> x;
	for (auto &x : b)
		cin >> x;
	vector sta(20, vector<int>(n)), stb = sta;
	for (int i = 0; i < n; ++i) {
		sta[0][i] = a[i];
		stb[0][i] = b[i];
	}
	for (int j = 1; (1 << j) <= n; ++j)
		for (int i = 0; i + (1 << j) <= n; ++i) {
			sta[j][i] = gcd(sta[j - 1][i], sta[j - 1][i + (1 << (j - 1))]);
			stb[j][i] = gcd(stb[j - 1][i], stb[j - 1][i + (1 << (j - 1))]);
		}
	auto gcda = [&](int l, int r) -> int {
		if (l > r || r >= n) return 0;
		int len = lg[r - l + 1];
		return gcd(sta[len][l], sta[len][r - (1 << len) + 1]);
	};
	auto gcdb = [&](int l, int r) -> int {
		if (l > r || r >= n) return 0;
		int len = lg[r - l + 1];
		return gcd(stb[len][l], stb[len][r - (1 << len) + 1]);
	};
	i64 ans = 0, cnt = 0;
	auto upd = [&](int x, int c) {
		if (x > ans) {
			ans = x;
			cnt = c;
		} else if (x == ans) cnt += c;
	};
	for (int L = 0; L < n; ++L) {
		int d1 = a[L], d2 = b[L], sd1 = gcda(L + 1, n - 1), sd2 = gcdb(L + 1, n - 1);
		int R = L;
		while (R < n) {
			int l = R, r = n - 1, mid, res = n;
			// res: where something get wrong
			while (l <= r) {
				mid = (l + r) >> 1;
				if (d1 != gcda(L, mid) || d2 != gcdb(L, mid)
					|| sd1 != gcda(mid + 1, n - 1) || sd2 != gcdb(mid + 1, n - 1)) {
					res = mid, r = mid - 1;
				} else l = mid + 1;
			}
			int D1 = gcd(gcda(0, L - 1), d2, sd1);
			int D2 = gcd(gcdb(0, L - 1), d1, sd2);
			upd(D1 + D2, res - R), R = res;
			d1 = gcda(L, R), d2 = gcdb(L, R);
			sd1 = gcda(R + 1, n - 1), sd2 = gcdb(R + 1, n - 1);
		}
	}
	cout << ans << " " << cnt << '\n';
}

int main() {
#ifdef IOS
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
#endif
#ifdef MULTI
	int TestCase = 1;
	for (cin >> TestCase; TestCase--;) solve();
#else
	solve();
#endif
	return 0;
}

// D solution:O(n log^2 n)

// 枚举一个 l, 然后对于一个 r,不难发现后缀 gcd 不同的 r 最多有 log n 种,gcd([l, r]) 最多也有 log n 种
// 可以预处理出所有不同的后缀 r 的位置,值域 gcd([l, r]) 不同的位置,线段树上二分即可,时间复杂度 O(n log^2 n) 可以得到所有 l 和有意义的 r
// 可不可以不用线段树二分?可以,查询区间 gcd,这是 st 表的问题。不过时间复杂度还是 O(n log n log V)

E

没看。

posted @ 2024-09-15 00:56  lingfunny  阅读(776)  评论(2编辑  收藏  举报