VP Educational Codeforces Round 32
A. Local Extrema
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int ans = 0;
for (int i = 1; i + 1 < n; ++ i) {
if ((a[i] > a[i - 1] && a[i] > a[i + 1]) || (a[i] < a[i - 1] && a[i] < a[i + 1])) {
++ ans;
}
}
std::cout << ans << "\n";
}
B. Buggy Robot
题意:给你一个操作序列,要求保留最多的操作,使得最后回到原点。
显然上下操作相等,左右操作相等。两个分别取最小值加起来。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::map<char, int> mp;
for (auto & c : s) {
mp[c] += 1;
}
int ans = std::min(mp['L'], mp['R']) * 2 + std::min(mp['U'], mp['D']) * 2;
std::cout << ans << "\n";
}
C. K-Dominant Character
题意:求一个最小的\(k\),使得所有长度为\(k\)的子串都包含至少一个相同的字符。
二分。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
std::vector<std::array<int, 26>> pre(n + 1);
std::fill(pre[0].begin(), pre[0].end(), 0);
for (int i = 0; i < n; ++ i) {
pre[i + 1] = pre[i];
pre[i + 1][s[i] - 'a'] += 1;
}
auto check = [&](int m) -> bool {
std::array<bool, 26> st{};
std::fill(st.begin(), st.end(), true);
for (int i = 1; i + m - 1 <= n; ++ i) {
for (int j = 0; j < 26; ++ j) {
st[j] &= pre[i + m - 1][j] - pre[i - 1][j] > 0;
}
}
return std::count(st.begin(), st.end(), true) > 0;
};
int l = 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << l << "\n";
}
D. Almost Identity Permutations
题意:给一个\(n\)和\(k\)。求所有\(n\)的排列里有小于等于\(k\)个\(p_i \neq i\)的排列数量。
\(k\)很小,可以分类讨论。
当\(k=0\),有一个排列。
当\(k=1\),没有满足的排列。
当\(k=2\),相当于找两个数交换,方案是\(C(n, 2)\)。
当\(k=3\),相当于找三个数,然后它们都不能在自己位置上的数,那么可以发现只有\(2, 1, 3\)和\(3, 1, 2\)这两类满足。所以是\(C(n, 3) \times 2\)。
当\(k=4\),和\(k=3\)一个思路,通过打表或者手算发现,选出的\(4\)个数有\(9\)种合法的排列。方案数位\(C(n, 4) \times 9\)。
点击查看代码
void solve() {
i64 n, k;
std::cin >> n >> k;
i64 ans = 0;
if (k >= 1) {
ans += 1;
}
if (k >= 2) {
ans += n * (n - 1) / 2;
}
if (k >= 3) {
ans += n * (n - 1) * (n - 2) / 6 * 2;;
}
if (k >= 4) {
//2 1 4 3
//2 3 4 1
//2 4 1 3
//3 1 4 2
//3 4 2 1
//3 4 1 2
//4 1 2 3
//4 3 2 1
//4 3 1 2
ans += n * (n - 1) * (n - 2) * (n - 3) / 24 * 9;
}
std::cout << ans << "\n";
}
E. Maximum Subsequence
题意:从\(n\)个数里选若干个个数,使得它们的和对\(m\)取模最大。
\(n\)只有\(35\),貌似是搜索和枚举。但直接枚举复杂度是\(2^n\)次方,无法通过。但我们可以分成两部分枚举,找出前\(\frac{n}{2}\)数可以凑出的所有值,以及后面\(\frac{n}{2}\)数可以凑出的所有值,这样复杂度是\(2 \times 2^{\frac{n}{2}}\)。
然后想办法把这两部分凑起来,可以枚举一部分,进行分类讨论,如果\(x_i + y_ i < m\),那么我们可以对于前部分的每个值二分找后部分满足 条件的最大值。如果\(x_i + y_i > m\),那么因为\(x_i, y_i\)都小于\(m\),所以我们应该取最大的\(y_i\)。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
a[i] %= m;
}
std::vector<int> b, c;
for (int i = 0; i < n; ++ i) {
if (i < n / 2) {
b.push_back(a[i]);
} else {
c.push_back(a[i]);
}
}
std::set<int> fb;
fb.insert(0);
for (auto & x : b) {
auto g = fb;
for (auto & y : fb) {
g.insert((x + y) % m);
}
fb = g;
}
std::set<int> fc;
fc.insert(0);
for (auto & x : c) {
auto g = fc;
for (auto & y : fc) {
g.insert((x + y) % m);
}
fc = g;
}
int ans = std::max(*fb.rbegin(), *fc.rbegin());
for (auto & x : fb) {
ans = std::max(ans, (x + *fc.rbegin()) % m);
auto it = fc.lower_bound(m - x);
if (it != fc.begin()) {
-- it;
ans = std::max(ans, x + *it);
}
}
std::cout << ans << "\n";
}
F. Connecting Vertices
题意:给出一个正多边形,你要用\(n-1\)条线连接它们使得形成一棵树,每条线连两个点,且任意两条线不能相交。求方案数。
考虑区间\(dp\)。\(f[i][j][0/1]\)代表\(i\)到\(j\)连成一棵树且\(i, j\)有没有连边的方案数。那么如果\(i, j\)有边,就可以连接它们,枚举一个中间点\(k\),使得本来区间是两棵树\([i, k], [k + 1, j]\)用\(i, j\)这条边连接它们,转移方程为\(f[i][j][0] = (f[i][k][0] + f[i][k][1]) \times (f[k + 1][j][0] + f[k + 1][j][1])\)。
然后考虑\(i, j\)不连边,那么我们要找一个中间点\(k\)使得\([i, k - 1], [k, j]\)加上\(i, k\)这条边后合并,那么有\(f[i][j][1] = f[i][k][0] \times (f[k][j][0] + f[k][j][1])\)。
代码省略取模类。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector g(n, std::vector<int>(n));
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
std::cin >> g[i][j];
}
}
std::vector f(n, std::vector(n, std::array<Z, 2>{0, 0}));
for (int len = 1; len <= n; ++ len) {
for (int i = 0; i + len - 1 < n; ++ i) {
int j = i + len - 1;
if (len == 1) {
f[i][j][0] = 1;
} else if (len == 2) {
f[i][j][0] = g[i][j];
} else {
if (g[i][j]) {
for (int k = i; k < j; ++ k) {
f[i][j][0] += (f[i][k][0] + f[i][k][1]) * (f[k + 1][j][0] + f[k + 1][j][1]);
}
}
for (int k = i; k < j; ++ k) {
if (g[i][k]) {
f[i][j][1] += f[i][k][0] * (f[k][j][0] + f[k][j][1]);
}
}
}
}
}
std::cout << f[0][n - 1][0] + f[0][n - 1][1] << "\n";
}
G. Xor-MST
题意:给你一个数组,它们两两连边,边权为两个数的异或和。求最小生成树。
考虑分治,从高位到低位,我们按\(bit\)分组,然后递归处理这两部分在计算它们之间的贡献,因为这两部分处理后,每个部分内部已经是一棵生成树了,那么只需要在一部分里选一个往另一部连边就行。我们需要的是异或最小,那么可以把一部分存到\(01Trie\)里,另一部分直接查询。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 2e5 + 5;
int Tr[N * 30][2];
struct Trie {
int idx;
Trie() {
idx = 0;
Tr[0][0] = Tr[0][1] = 0;
}
int new_node() {
++ idx;
Tr[idx][0] = Tr[idx][1] = 0;
return idx;
}
void insert(int x) {
int p = 0;
for (int i = 29; i >= 0; -- i) {
int s = x >> i & 1;
if (!Tr[p][s]) {
Tr[p][s] = new_node();
}
p = Tr[p][s];
}
}
int query(int x) {
int res = 0;
int p = 0;
for (int i = 29; i >= 0; -- i) {
int s = x >> i & 1;
if (Tr[p][s]) {
p = Tr[p][s];
} else {
res += 1 << i;
p = Tr[p][!s];
}
}
return res;
}
};
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
auto work = [&](auto & self, std::vector<int> & A, int k) -> i64 {
if (A.empty() || k < 0) {
return 0;
}
std::vector<int> a, b;
for (auto & x : A) {
if (x >> k & 1) {
a.push_back(x);
} else {
b.push_back(x);
}
}
i64 ans = self(self, a, k - 1) + self(self, b, k - 1);
if (a.empty() || b.empty()) {
return ans;
}
int min = 1 << 30;
if (a.size() < b.size()) {
Trie tr;
for (auto & x : a) {
tr.insert(x);
}
for (auto & x : b) {
min = std::min(min, tr.query(x));
}
} else {
Trie tr;
for (auto & x : b) {
tr.insert(x);
}
for (auto & x : a) {
min = std::min(min, tr.query(x));
}
}
return ans + min;
};
std::cout << work(work, a, 29) << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}