Codeforces Round 972 (Div. 2)

lingfunny發表於2024-09-15

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

沒看。

相關文章