VP Educational Codeforces Round 19
A. k-Factorization
题意:选\(k\)个大于\(1\)的数,使得乘积为\(n\)。
我们前面让每个数越小越好,然后让最后一个数补上就行,这样能凑出来最多的数。那么就直接分解质因子,这样能安排最多的数。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a;
for (int i = 2; i * i <= n && a.size() + 1 < k; ++ i) {
if (n % i == 0) {
while (n % i == 0 && a.size() + 1 < k) {
a.push_back(i);
n /= i;
}
}
}
if (a.size() < k && n > 1) {
a.push_back(n);
}
if (a.size() < k) {
std::cout << -1 << "\n";
} else {
for (int i = 0; i < k; ++ i) {
std::cout << a[i] << " \n"[i == k - 1];
}
}
}
B. Odd sum
题意:找数组的一个子序列,使得和是奇数,并且和最大。
要使得和是奇数,发现和拿多少偶数没关系,我们只需要保证拿奇数个奇数就行。那么偶数大于0的都拿上。然后奇数从大到小排序,取奇数位置前缀和的最大值。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a, b;
for (int i = 0; i < n; ++ i) {
int x;
std::cin >> x;
if (x % 2 == 0) {
b.push_back(x);
} else {
a.push_back(x);
}
}
std::sort(a.begin(), a.end(), std::greater<int>());
std::sort(b.begin(), b.end(), std::greater<int>());
i64 ans = 0;
for (auto & x : b) {
if (x > 0) {
ans += x;
}
}
i64 sum = -1e18, pre = 0;
for (int i = 0; i < a.size(); ++ i) {
pre += a[i];
if (i % 2 == 0) {
sum = std::max(sum, pre);
}
}
std::cout << ans + sum << "\n";
}
C. Minimal string
题意:你有一个字符\(s\),和两个空字符串\(t, u\)。你每次可以把\(s\)的第一个字符给到\(t\)的最后,或者把\(t\)的最后一个字符给\(u\)。要使得最终\(u\)字典序最小。
\(t\)的作用就是不要让字典序大的字符提前到\(u\)里,所以我们维护\(s\)的一个后缀\(min\)。如果当前\(s\)的第一个字符是后缀最小的,直接给\(u\),否则给\(t\)。或者\(t\)的最后的字符是\(s\)这个后缀最小的,也直接给\(u\)。这样就能保证\(u\)的前面拿到了最小的字典序。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
std::string t;
std::vector<int> suf(n + 1, 30);
for (int i = n - 1; i >= 0; -- i) {
suf[i] = std::min(s[i] - 'a', suf[i + 1]);
}
std::string ans;
for (int i = 0; i < n; ++ i) {
while (t.size() && t.back() - 'a' <= suf[i]) {
ans += t.back();
t.pop_back();
}
if (s[i] - 'a' <= suf[i]) {
ans += s[i];
} else {
t += s[i];
}
}
while (t.size()) {
ans += t.back();
t.pop_back();
}
std::cout << ans << "\n";
}
D. Broken BST
题意:给你一棵普通二叉树,如果把它当成二叉搜索树来用,节点的值中有多少不能被搜索到。
这题坑点就是,是问有多少值不能被搜到,而不是有多少节点。所以如果一个值出现在多个节点,只要有一个节点能被搜到这个值就能被搜到。
考虑每个节点什么时候能被搜到,发现搜索下来就是一条路径,如果这个路径往左拐一次,那么该节点的值就要小于这个点的值,如果右拐一次,那么值就要大于这个点的值,发现最后到该节点是就形成了一个取值区间,如果这个点的权值在这个区间就能被取到,那么我们就这样dfs遍历一遍树就可以求出答案。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> v(n + 1), l(n + 1), r(n + 1);
std::vector<int> in(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> v[i] >> l[i] >> r[i];
if (l[i] != -1) {
++ in[l[i]];
}
if (r[i] != -1) {
++ in[r[i]];
}
}
std::map<int, int> mp;
auto dfs = [&](auto self, int u, int min, int max) -> void {
if (min < v[u] && v[u] < max) {
mp[v[u]] = 1;
}
if (l[u] != -1) {
self(self, l[u], min, std::min(max, v[u]));
}
if (r[u] != -1) {
self(self, r[u], std::max(min, v[u]), max);
}
};
int root = 0;
for (int i = 1; i <= n; ++ i) {
if (in[i] == 0) {
root = i;
}
}
dfs(dfs, root, -1e9, 1e9);
int ans = 0;
for (int i = 1; i <= n; ++ i) {
ans += mp[v[i]];
}
std::cout << n - ans << "\n";
}
E. Array Queries
题意:一个长度为\(n\)的数组,\(q\)次询问,给一个\(p, k\),每次使得\(p = p + a_p + k\),直到\(p > n\)。求每次询问给出的数能操作几次。
这几天碰到了好几道根号分治题。
总长度只有\(n\),那么如果\(k > \sqrt(n)\),不管\(a\)里的值有多小,也最多跳\(\sqrt(n)\)次,这种情况直接模拟即可。 对于\(k \leq \sqrt(n)\),我们可以\(dp\)预处理,\(f[i][j]\)为跳到\(i\)时\(k = j\)还需要跳几次出去。那么从后往前转移就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
int m = std::sqrt(n);
std::vector f(n + 1, std::vector<int>(m + 1));
for (int i = n; i >= 1; -- i) {
for (int j = 1; j <= m; ++ j) {
f[i][j] = i + j + a[i] > n ? 1 : f[i + j + a[i]][j] + 1;
}
}
int q;
std::cin >> q;
while (q -- ) {
int p, k;
std::cin >> p >> k;
if (k <= m) {
std::cout << f[p][k] << "\n";
} else {
int ans = 0;
while (p <= n) {
p = p + a[p] + k;
++ ans;
}
std::cout << ans << "\n";
}
}
}
F. Mice and Holes
题意:坐标上有一些老鼠和一些洞,每个洞有容量限制\(c\),求让所有老鼠进洞所走的最小距离。
开始以为是网络流,但内存只给了256M, 直接\(mle\)。
考虑\(dp\),老鼠和洞按坐标从小到大排序后,\(f[i][j]\)表示前\(i\)个老鼠进前\(j\)个洞的最小总距离。 预处理\(sum[i][j]\)表示前\(i\)个老鼠都进\(j\)这个洞的总距离,那么就有转移方程\(f[i][j] = \min_{k=\max(0, i - c_j)}^{i} f[k][j - 1] + sum[i][j] - sum[k][j]\)。于是可以写出如下代码:
点击查看代码
f[0][0] = 0;
for (int j = 1; j <= m; ++ j) {
for (int i = 0; i <= n; ++ i) {
for (int k = std::max(0, i - b[j].second); k <= i; ++ k) {
f[i][j] = std::min(f[i][j], f[k][j - 1] + sum[i][j] - sum[k][j]);
}
}
}
但这个是\(O(n^3)\)的时间复杂度,无法通过,我们把转移方程处理一下:\(f[i][j] = sum[i][j] + \min_{k=\max(0, i - c_j)}^{i} f[k][j - 1] - sum[k][j]\),发现后面一部分和\(i\)无关,那么可以考虑单调队列优化。于是就做完了。
注意内存卡的很死,需要用滚动数组优化。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
std::vector<std::pair<int, int>> b(m + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
for (int i = 1; i <= m; ++ i) {
std::cin >> b[i].first >> b[i].second;
}
std::sort(a.begin() + 1, a.end());
std::sort(b.begin() + 1, b.end());
const i64 inf = 1e18;
std::vector<i64> f(n + 1, inf);
std::vector<i64> sum(n + 1);
f[0] = 0;
std::vector<int> q(n + 10);
for (int j = 1; j <= m; ++ j) {
std::vector<i64> g(n + 1, inf);
for (int i = 1; i <= n; ++ i) {
sum[i] = sum[i - 1] + std::abs(a[i] - b[j].first);
}
int hh = 0, tt = -1;
for (int i = 0; i <= n; ++ i) {
while (hh <= tt && q[hh] < i - b[j].second) {
++ hh;
}
while (hh <= tt && f[q[tt]] - sum[q[tt]] >= f[i] - sum[i]) {
-- tt;
}
q[ ++ tt] = i;
g[i] = f[q[hh]] + sum[i] - sum[q[hh]];
}
f = g;
}
i64 ans = f[n] == inf ? -1 : f[n];
std::cout << ans << "\n";
}