Codeforces Round #853 (Div. 2) 题解
Codeforces Round #853 (Div. 2) 题解
ABCD
Codeforces Round #853 (Div. 2) | 萌新实况被吊打 | ABCD 题解
E. Serval and Music Game
分两种情况讨论:
- \(\lfloor\frac{s_n}{x}\rfloor=\lceil\frac{s_n}{x}\rceil\).
- \(\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)\)。
本文来自博客园,作者:lingfunny,转载请注明原文链接:https://www.cnblogs.com/lingfunny/p/17156323.html