Codeforces Round #853 (Div. 2) 题解

Codeforces Round #853 (Div. 2) 题解

ABCD

Codeforces Round #853 (Div. 2) | 萌新实况被吊打 | ABCD 题解

E. Serval and Music Game

分两种情况讨论:

  1. \(\lfloor\frac{s_n}{x}\rfloor=\lceil\frac{s_n}{x}\rceil\).
  2. \(\lfloor\frac{s_n}{x}\rfloor + 1=\lceil\frac{s_n}{x}\rceil\).

对于第一种,\(x\mid s_n\),即查询 \(s_i\)\(k=\frac{s_n}{x}\) 的倍数有几个。

显然 \(k\mid s_n\),则当 \(k\mid s_i\) 时,显然有 \(k\mid \gcd(s_i, s_n)\)。那么我们可以把 \(s_i\) 扔到 \(cnt_{\gcd(s_i, s_n)}\) 上,查询的时候我们遍历 \(s_n\) 的因子,找出所有 \(k\mid d\mid s_n\),然后统计 \(cnt_d\)

这一步的时间复杂度是 \(O(n\log s_n + s_n)\)。(一共有 \(\sqrt {s_n}\)\(k\),遍历因子是 \(\sqrt {s_n}\) 的)

对于第二种,设 \(k=\lfloor\frac{s_n}{x}\rfloor\),则查询 \(s_i=pk + q(k+1)\),则 \(s_i\bmod k \le \lfloor\frac{s_i}{k}\rfloor\) 时都成立。

换个角度考虑,\(pk + q(k + 1)\) 可以表示出 \(ak + b~(b\le a)\),所有区间即为 \([k, k + 1], [2k, 2k + 2],\dots\)

\(s_i\) 在值域上做前缀和即可 \(O(1)\) 查询这些区间。

看起来这里似乎有 \(\frac{s_n}{k}\) 个区间,但实际上当 \(a\ge k\) 的时候,就已经可以表示任何数了。所以只有 \(k\) 个区间。

\(k\le \sqrt {s_n}\),我们暴力计算上面那些 \(O(k)\) 个区间。

对于 \(k\ge \sqrt{s_n}\),显然 \(ak + b\le s_n \to a\le\frac{s_n}{k}=\sqrt {s_n}\),所以区间也只有 \(O(\sqrt s_n)\) 种。

所以对于任意一个 \(k\),查询都是 \(O(\sqrt {s_n})\) 的,一共有 \(O(\sqrt {s_n})\)\(k\),时间复杂度 \(O(s_n)\)

总时间复杂度 \(O(n\log s_n + s_n)\)

// Problem: E. Serval and Music Game
// URL: https://codeforces.com/contest/1789/problem/E
// Group: Codeforces - Codeforces Round #853 (Div. 2)
// Time: 2023-02-25 22:20
// Author: lingfunny

#include <bits/stdc++.h>
using LL = long long;
using uint = unsigned;
using namespace std;

constexpr int mod = 998244353;
// assume -mod <= x < 2mod
int normZ(int x) {
	if (x < 0) x += mod;
	if (x >= mod) x -= mod;
	return x;
}
template <typename T> T qpow(T x, LL k) {
	T res = 1;
	for (; k; k >>= 1, x *= x)
		if (k & 1) res *= x;
	return res;
}

struct Z {
	int x;
	Z(int x = 0) : x(normZ(x)) {}
	Z(LL x) : x(normZ(x % mod)) {}
	int val() const { return x; }
	Z operator-() const { return Z(normZ(mod - x)); }
	Z inv() const {
		assert(x != 0);
		return qpow(*this, mod - 2);
	}
	Z &operator*=(const Z &rhs) {
		x = (LL)x * rhs.x % mod;
		return *this;
	}
	Z &operator+=(const Z &rhs) {
		x = normZ(x + rhs.x);
		return *this;
	}
	Z &operator-=(const Z &rhs) {
		x = normZ(x - rhs.x);
		return *this;
	}
	Z &operator/=(const Z &rhs) { return *this *= rhs.inv(); }
	friend Z operator*(const Z &lhs, const Z &rhs) {
		Z res = lhs;
		res *= rhs;
		return res;
	}
	friend Z operator+(const Z &lhs, const Z &rhs) {
		Z res = lhs;
		res += rhs;
		return res;
	}
	friend Z operator-(const Z &lhs, const Z &rhs) {
		Z res = lhs;
		res -= rhs;
		return res;
	}
	friend Z operator/(const Z &lhs, const Z &rhs) {
		Z res = lhs;
		res /= rhs;
		return res;
	}
};

const int mxs = 1e7 + 10, mxn = 1e6 + 10;

int TestCase, n, a[mxn], s[mxs], R[mxs], cnt[mxs];
Z ans;

signed main() {
	for (scanf("%d", &TestCase); TestCase--;) {
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) scanf("%d", a + i), ++s[a[i]];
		for (int i = 1; i <= a[n]; ++i) s[i] += s[i - 1];
		for (int i = 0; i <= a[n]; ++i) {
			cnt[i] = 0;R[i] = -1;
		}
		for (int i = 1; i <= n; ++i) ++cnt[gcd(a[i], a[n])];
		for (int I = 1; I <= a[n]; ++I)
			if (a[n] % I) {
				int k = a[n] / I;
				if (!~R[k]) {
					R[k] = 0;
					for (int i = 1; i * k <= a[n] && i < k; ++i) {
						int r = min(i * k + i, a[n]), l = i * k;
						R[k] += s[r] - s[l - 1];
					}
					if ((LL)k * k <= a[n]) R[k] += s[a[n]] - s[k * k - 1];
				}
				ans += (LL)R[k] * I % mod;
			} else {
				int sum = 0;
				int k = a[n] / I;
				for (int d = 1; d * d <= a[n]; ++d)
					if (a[n] % d == 0) {
						if (d % k == 0) sum += cnt[d];
						int D = a[n] / d;
						if (D != d && D % k == 0) sum += cnt[D];
					}
				ans += (LL)sum * I % mod;
			}
		printf("%d\n", ans.val()), ans = 0;
		for (int i = 1; i <= a[n]; ++i) s[i] = 0;
	}
	return 0;
}

F. Serval and Brain Power

有一个限制条件是 \(k\lvert T'\rvert \le \lvert S\rvert = 80\)

我们对左边这个式子平衡规划。

对于 \(k< 5\) 时,显然 \(k=2\) 包含 \(k=4\)

考虑 \(k=2\),我们暴力枚举 \(S = S_1 + S_2\)\(O(n)\) 种拆解方式,然后做 \(\operatorname{LCS}(S_1, S_2)\)。dp 的时间复杂度是 \(O(n^2)\) 的,这里的总时间复杂度是 \(O(n^3)\) 的。

考虑 \(k=3\),同理的,枚举 \(S=S_1 + S_2 + S_3\)\(O(n^2)\) 种拆解方式,然后做 \(\operatorname{LCS}(S_1, S_2, S_3)\)。dp 的时间复杂度是 \(O(n^3)\) 的,总时间复杂度是 \(O(n^5)\) 的。

有一个好事是,显然我们的拆解方式是不满的。我们考虑这个过程的组合意义。我们把 \(n\) 个白球和 \(5\) 个黑球排成一列,求本质不同的方案数。

这里的第 \(2\) 和第 \(4\) 个黑球是序列的拆解方案数,而 \(1, 3, 5\) 可以放在任意位置,方案数为 \(\prod \vert S_i\vert\)

则方案数为 \(\binom{n+5}{5} \approx 3\times10^7\),还是很稳的。

对于 \(k\ge 5\),显然有 \(\vert T'\vert \le 16\),并且 \(T'\) 出现了 \(5\) 次以上。如果把 \(S\) 划分为五段长度相等的部分,则 \(T'\) 至少会完整地出现在某个段中。

于是我们对每个段,\(O(2^{16})\) 枚举所有可能的子序列,并进行一次 \(O(n)\) 的贪心匹配。时间复杂度 \(5\times2^{16}\times 80 = 2\times10^7\),也是很稳的。

// Problem: F. Serval and Brain Power
// URL: https://codeforces.com/contest/1789/problem/F
// Group: Codeforces - Codeforces Round #853 (Div. 2)
// Time: 2023-02-25 22:20
// Author: lingfunny

#include <bits/stdc++.h>
using LL = long long;
using uint = unsigned;
using namespace std;
const int mxn = 85;

int n;
char s[mxn], t[mxn];

signed main() {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	auto solve2 = [&](char s1[], char s2[], int n, int m) {
		static int f[mxn][mxn];
		for (int i = 0; i <= n; ++i)
			for (int j = 0; j <= m; ++j) f[i][j] = 0;
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= m; ++j) f[i][j] = max({ f[i - 1][j], f[i][j - 1], f[i - 1][j - 1] + (s1[i] == s2[j]) });
		return f[n][m] * 2;
	};
	auto solve3 = [&](char s1[], char s2[], char s3[], int n, int m, int q) {
		static int f[mxn][mxn][mxn];
		for (int i = 0; i <= n; ++i)
			for (int j = 0; j <= m; ++j)
				for (int k = 0; k <= q; ++k) f[i][j][k] = 0;
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= m; ++j)
				for (int k = 1; k <= q; ++k)
					f[i][j][k] = max({ f[i - 1][j][k], f[i][j - 1][k], f[i][j][k - 1], f[i - 1][j - 1][k - 1] + (s1[i] == s2[j] && s2[j] == s3[k]) });
		return f[n][m][q] * 3;
	};
	auto match = [&](int m) {
		int cnt = 0;
		for (int i = 1; i <= n; ++i)
			if (s[i] == t[cnt % m]) ++cnt; 
		return cnt >= m * 2 ? cnt / m * m : 0;
	};
	int ans = 0;
	for (int i = 1; i < n; ++i) ans = max(ans, solve2(s, s + i, i, n - i));
	for (int i = 1; i < n; ++i)
		for (int j = 1; i + j < n; ++j) ans = max(ans, solve3(s, s + i, s + i + j, i, j, n - i - j));
	for (int l = 1, r; l <= n; l = r + 1) {
		r = min(l + (n + 4) / 5, n);
		int len = r - l + 1, tot = 1 << len;
		for (int S = 1; S < tot; ++S) {
			int m = 0;
			for (int i = 0; i < len; ++i)
				if ((S >> i) & 1) t[m++] = s[l + i];
			ans = max(ans, match(m));
		}
	}
	printf("%d\n", ans);
	return 0;
}

END

E 和 F 在复杂度分析上都很有意思!

E 的「查询 \(i\) 的倍数在 \(A\) 中的出现次数」也很值得思考。这里的做法(代码里)是 \(O(n\log v)\) 处理 \(\gcd(s_i, s_n)\) 的。实际上如果设函数 \(f(x) = \gcd(x, s_n)\),这个东西是个积性函数。可以用欧拉筛在 \(O(s_n)\) 内算出来。此题可以做到准 \(O(n + s_n)\)

posted @ 2023-02-26 11:09  lingfunny  阅读(357)  评论(4编辑  收藏  举报