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";
}
posted @ 2025-02-27 15:39  maburb  阅读(24)  评论(0)    收藏  举报