Codeforces Round #853 (Div. 2) 题解

Codeforces Round #853 (Div. 2) 题解

ABCD

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

E. Serval and Music Game

分两种情况讨论:

  1. snx=snx.
  2. snx+1=snx.

对于第一种,xsn,即查询 sik=snx 的倍数有几个。

显然 ksn,则当 ksi 时,显然有 kgcd(si,sn)。那么我们可以把 si 扔到 cntgcd(si,sn) 上,查询的时候我们遍历 sn 的因子,找出所有 kdsn,然后统计 cntd

这一步的时间复杂度是 O(nlogsn+sn)。(一共有 snk,遍历因子是 sn 的)

对于第二种,设 k=snx,则查询 si=pk+q(k+1),则 simodksik 时都成立。

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

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

看起来这里似乎有 snk 个区间,但实际上当 ak 的时候,就已经可以表示任何数了。所以只有 k 个区间。

ksn,我们暴力计算上面那些 O(k) 个区间。

对于 ksn,显然 ak+bsnasnk=sn,所以区间也只有 O(sn) 种。

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

总时间复杂度 O(nlogsn+sn)

// 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|T||S|=80

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

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

考虑 k=2,我们暴力枚举 S=S1+S2O(n) 种拆解方式,然后做 LCS(S1,S2)。dp 的时间复杂度是 O(n2) 的,这里的总时间复杂度是 O(n3) 的。

考虑 k=3,同理的,枚举 S=S1+S2+S3O(n2) 种拆解方式,然后做 LCS(S1,S2,S3)。dp 的时间复杂度是 O(n3) 的,总时间复杂度是 O(n5) 的。

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

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

则方案数为 (n+55)3×107,还是很稳的。

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

于是我们对每个段,O(216) 枚举所有可能的子序列,并进行一次 O(n) 的贪心匹配。时间复杂度 5×216×80=2×107,也是很稳的。

// 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(nlogv) 处理 gcd(si,sn) 的。实际上如果设函数 f(x)=gcd(x,sn),这个东西是个积性函数。可以用欧拉筛在 O(sn) 内算出来。此题可以做到准 O(n+sn)

posted @   lingfunny  阅读(366)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示