VP Educational Codeforces Round 60 (Rated for Div. 2)


A. Best Subsegment

题意:找出最长的平均值最大的子区间。

显然平均值最大的子区间的所有元素都数组最大值。因为一旦有一个数不是最大值,那么平均数显然小于最大值,而单个最大值的平均数就是最大值,可以发现最大值就是最大平均数的值。
那么找最长的连续最大值就行。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}    

	int max = *std::max_element(a.begin(), a.end());
	int ans = 0;
	for (int i = 0; i < n; ++ i) {
		if (a[i] == max) {
			int j = i;
			while (j < n && a[i] == a[j]) {
				++ j;
			}

			ans = std::max(ans, j - i);
			i = j - 1;
		}
	}

	std::cout << ans << "\n";
}

B. Emotes

题意:\(n\)个数,操作\(m\)次,每次选一个数得到它的值,每个数不能连续选超过\(k\)次。求最大价值。

我们只会操作最大值和次大值(次大值可能等于最大值)。那么每次操作\(k\)次最大值,然后操作一次次大值,如此循环直到操作完\(m\)次。

点击查看代码
void solve() {
    i64 n, m, k;
    std::cin >> n >> m >> k;
    std::vector<i64> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::ranges::sort(a, std::greater<i64>());
    i64 t = m / (k + 1);
    i64 ans = t * (a[0] * k + a[1]) + (m - t * (k + 1)) * a[0];
    std::cout << ans << "\n";
}

C. Magic Ship

题意:从\((x1, y1)\)走到\((x2, y2)\),给你一个循环序列,每个位置代表一个移动的方向,每次移动必须按照序列给定的方向移动一格,然后每次你要再选择一个方向移动一格。求走到\((x2, y2)\)的最小时间。

如果在时间\(t\),我们可以用前缀和计算移动序列对坐标的贡献,然后可以得到通过这个序列\(x\)\(y\)分别移动到了哪里,我们还有剩下\(t\)次自己选择的移动,那么\(t\)需要大于等于\(|x - x1| + |y - y2|\)。如果剩余步数是偶数,我们可以反复选两个相反的方向抵消,如果是奇数,无法到达。貌似无法做了,但还是有二分性的,如果\(t \geq |x - x1| + |y - y2|\)我们就认为它可以,那么因为\(t - |x - x1| + |y - y2|\)最小是\(0\),所有当这个差值为奇数的时候我们也认为它可以,任何缩小右边界,这样最终答案的这个差值一定是偶数。

点击查看代码
void solve() {
    i64 x1, y1, x2, y2;
    std::cin >> x1 >> y1 >> x2 >> y2;
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    std::vector<i64> prex(n + 1), prey(n + 1);
    for (int i = 0; i < n; ++ i) {
    	prex[i + 1] = prex[i];
    	prey[i + 1] = prey[i];
    	if (s[i] == 'U') {
    		prey[i + 1] += 1;
    	} else if (s[i] == 'D') {
    		prey[i + 1] -= 1;
    	} else if (s[i] == 'L') {
    		prex[i + 1] -= 1;
    	} else {
    		prex[i + 1] += 1;
    	}
    }

    auto check = [&](i64 d) -> i64 {
    	i64 t = d / n;
    	i64 x = x1 + prex[n] * t, y = y1 + prey[n] * t;
    	x += prex[d % n], y += prey[d % n];
    	return std::abs(x - x2) + std::abs(y - y2);
    };

    i64 l = 1, r = 1e16;
    while (l < r) {
    	i64 mid = l + r >> 1ll;
    	if (check(mid) <= mid) {
    		r = mid;
    	} else {
    		l = mid + 1;
    	}
    }

    if (check(l) > l) {
    	std::cout << -1 << "\n";
    } else {
    	std::cout << l << "\n";
    }
}

D. Magic Gems

题意:用价值为\(1\)和价值为\(m\)的物品填充\(n\)的空间,问有多少种填法。

根据经验,求方案数的题目要么是\(dp\)要么是组合数学,而这个题\(n\)的范围对于组合数学来说也太大了,有溢出风险,而且也无法做。那么考虑\(dp\)
\(f[i]\)\(i\)的空间的方案数,那么如果\(i < m, f[i] = 1\),否则\(f[i] = f[i - 1] + f[i - m]\)。但这个复杂度是\(O(n)\),也无法接受。
发现\(n\)很大,\(m\)很小。我们联想到矩阵快速幂。然后尝试构造矩阵,发现\([f_{i-1}, f_{i-2}, ... , f_{i-m}]\)如果要推出\([f_i, f_{i-1}, ... , f_{i-m+1}]\)。可以得到以下矩阵:

\[\left[ \begin{matrix} 1 & 1 & 0 & 0 & ...\\ 0 & 0 & 1 & 0 & ...\\ 0 & 0 & 0 & 1 & ...\\ ...\\ 1 & 0 & 0 & 0 & ...\\ \end{matrix} \right] \]

也就是\(mat[0][0] = mat[m - 1][0] = mat[i][i + 1] = 1\)

点击查看代码
const int mod = 1000000007, N = 110;

using Mat = std::array<std::array<int, N>, N>;

struct Matrix {
	Mat mat;
	Matrix() {
		init();
	}

	void init() {
		for (int i = 0; i < N; ++ i) {
			for (int j = 0; j < N; ++ j) {
				mat[i][j] = 0;
			}
		}
	}

	void one() {
		init();
		for (int i = 0; i < N; ++ i) {
			mat[i][i] = 1;
		}
	}
};

Matrix operator * (const Matrix & a, const Matrix & b) {
	Matrix res;
	for (int k = 0; k < N; ++ k) {
		for (int i = 0; i < N; ++ i) {
			for (int j = 0; j < N; ++ j) {
				res.mat[i][j] = (res.mat[i][j] + (i64)a.mat[i][k] * b.mat[k][j] % mod) % mod;
			}
		}
	}

	return res;
}

Matrix power(Matrix a, i64 b) {
	Matrix res;
	res.one();
	for (; b ; b >>= 1, a = a * a) {
		if (b & 1) {
			res = res * a;
		}
	}

	return res;
}

void solve() {
    i64 n, m;
    std::cin >> n >> m;
    Matrix a;
    for (int i = 0; i < m; ++ i) {
    	a.mat[i][i + 1] = 1;
    }

    a.mat[0][0] = a.mat[m - 1][0] = 1;
    auto ans = power(a, n);
    std::cout << ans.mat[0][0] << "\n";
}

E. Decypher the String

题意:交互题。有一个字符串和一个操作序列,我们不知道这个字符串和操作序列。每次操作是交换字符串两个位置的字符。选择给你操作完后的字符串,你可以询问三次,每次给出一个长度为\(n\)的字符串,会返回一个这个字符串经过操作序列所有操作后的字符串。求原字符串。

如果是一个排列,显然我们可以直接给出\(1, 2, .. , n - 1, n\),根据返回的数组就可以直到每个位置最终被交换到了哪个位置。
但这个题目字符集只有\(26\),无法构造一个长度为\(n\)的排列。不过我们可以继续询问\(26个a, 26个b, ... ,26个z\),以及\(26\times26个a, 26\times26个b, ... ,26\times26个z\)。发现\(26^3\)已经大于\(n\),所以我们只需要询问到这里。那么这两个字符串有什么用?在第二个字符串里,如果\(s_i\)变成了\(s_j\),那么说明其交换到了第\(s_j - 'a'\)个部分。但这两个信息依然无法确定位置,因为还有很多第二个字符串。那么经过第三个字符串的询问后,我们就可以知道是在哪一个第二个字符串的部分里。

点击查看代码
void solve() {
	std::string s;
	std::cin >> s;
	int n = s.size();
	auto ask = [&](const std::string & s) -> std::vector<int> {
		std::cout << "? " << s << std::endl;
		std::string t;
		std::cin >> t;
		std::vector<int> res(n);
		for (int i = 0; i < n; ++ i) {
			res[i] = t[i] - 'a';
		}

		return res;
	};

	std::string q1, q2, q3;
	for (int i = 0; i < n; ++ i) {
		q1 += (char)(i % 26 + 'a');
		q2 += (char)(i / 26 % 26 + 'a');
		q3 += (char)(i / 26 / 26 % 26 + 'a');
	}

	auto p1 = ask(q1);
	auto p2 = ask(q2);
	auto p3 = ask(q3);

	std::string ans(n, ' ');
	for (int i = 0; i < n; ++ i) {
		ans[p3[i] * 26 * 26 + p2[i] * 26 + p1[i]] = s[i];
	}

	std::cout << "! " << ans << std::endl;
}
posted @ 2025-04-16 18:47  maburb  阅读(14)  评论(0)    收藏  举报