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+ap+k,直到p>n。求每次询问给出的数能操作几次。

这几天碰到了好几道根号分治题。
总长度只有n,那么如果k>(n),不管a里的值有多小,也最多跳(n)次,这种情况直接模拟即可。 对于k(n),我们可以dp预处理,f[i][j]为跳到ik=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]=mink=max(0,icj)if[k][j1]+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(n3)的时间复杂度,无法通过,我们把转移方程处理一下:f[i][j]=sum[i][j]+mink=max(0,icj)if[k][j1]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 @   maburb  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示