Codeforces Round 1044 (Div. 2)


A. Redstone?

题意:给你一个数组,使得\(\frac{a_1}{a_2} \times \frac{a_2}{a_3} \times ... \times \frac{a_{n-1}}{a_n} = 1\)

发现式子其实是\(\frac{a_1}{a_n} = 1\)
那么只要有两个相同的数就\(yes\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::ranges::sort(a);
	for (int i = 0; i + 1 < n; ++ i) {
		if (a[i] == a[i + 1]) {
			std::cout << "YES\n";
			return;
		}
	}
	std::cout << "NO\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;
}

B. Villagers

题意:一个数组\(a\),每次选择\(i, j\)构成一条边,代价是\(\max(a_i, a_j)\),然后同时\(a_i -= \min(a_i, a_j), a_j -= \min(a_i, a_j)\)。求构成一棵树的最小代价。

发现\(i, j\)中较小的会变成\(0\),那么每次肯定选最大的两个,因为最大的数不管和谁一组代价都是它自己,所以选择把次大的消为\(0\)。然后两条边之间都可以选择变成\(0\)的那个点连边,那么代价就是\(0\)。那么我们只需要每次取出最大的两个就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::priority_queue<int> heap;
	for (int i = 0; i < n; ++ i) {
		heap.push(a[i]);
	}

	i64 ans = 0;
	for (int i = 1; i < n; ++ i) {
		int u = heap.top(); heap.pop();
		int v = heap.top(); heap.pop();
		ans += u;
		heap.push(0);
	}
	std::cout << ans << "\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;
}

C. The Nether

题意:交互题。一个有向无环图。你每次可以选出一些然后从中选一个起点,会告诉你只经过这些点的最长路径的长度。你需要求出一条最长路径。

把所有点选中就可以知道以起点开头的路径的最长长度。那么\(n\)次询问得到每个点的最长长度。然后选择一个最长的点,求它一个后面的点,这个点的长度一定是第二长的,那么可以把小于第二长的点都加入,然后依次判断一个第二长的点是不是最长点的后继。那么同样道理通过\([1, i - 1]\)长的点和长度为\(i+1\)的点组成的集合,依次判断一个长度为\(i\)的点是不是第\(i+1\)长的点的后继。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

int ask(int x, std::vector<int> & s) {
	std::cout << "? " << x << " " << s.size() << " ";
	for (auto & u : s) {
		std::cout << u << " ";
	}
	std::cout << std::endl;
	int res;
	std::cin >> res;
	return res;
}

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	std::ranges::iota(a, 1);
	int max = 0, s = -1;
	std::vector<std::vector<int>> g(n + 1);
	for (int i = 1; i <= n; ++ i) {
		int d = ask(i, a);
		if (d > max) {
			max = d;
			s = i;
		}
		g[d].push_back(i);
	}

	std::vector<int> ans;
	ans.push_back(s);
	for (int i = max - 1; i >= 1; -- i) {
		std::vector<int> b = ans;
		for (int j = 1; j < i; ++ j) {
			for (auto & x : g[j]) {
				b.push_back(x);
			}
		}

		for (auto & x : g[i]) {
			b.push_back(x);
			if (ask(s, b) == max) {
				ans.push_back(x);
				break;
			}
		}
	}

	std::cout << "! " << max << " ";
	for (auto & x : ans) {
		std::cout << x << " ";
	}
	std::cout << std::endl;
}

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;
}

D. Chicken Jockey

题意:一个高度为\(n\)的塔,用\(a\)表示,每次可以把一个\(a_i\)减一,如果\(a_i = 0\),则\([i + 1, n]\)都会掉下来,\(i+1\)受到\(i\)点伤害,也就是高度。然后\([i + 1, n]\)组成了新的塔,高度从\(1\)开始,然后重复判断,如果底部小于等于\(0\),则上面的又会掉下来。求把所有\(a_i\)小于等于\(0\)的最小操作数。

如果先把\(i\)变成\(0\)再把\(j\)变成\(0\),其中\(i < j\),那么肯定不如先把\(j\)变成\(0\)然后\(i\)变成\(0\)。也就是我们是从高往低操作。
那么假设操作出了\([i, j]\)这个区间的作为新塔,肯定是从下到上攻击了,因为如果\(a_i\)不是最底层的位置,我们先把它减为\(0\)让上面的掉下来,不如先把它变成\(0\)让上面的掉下来,然后再操作最底层下面的位置形成这个新的塔。也就是如果攻击\(i-1\)使得\([i, j]\)掉下来,这个新的塔的攻击顺序是固定的。
那么记\(f_i\)\([i, n]\)都消完的最小代价。则枚举\(j\),意味着\([i + 1, j]\)这个区间掉下来,那么\(f_i = a_i + \min(\max(a_{i+1} - i, 0) + sum_j - sum_{i+1} - (j - (i + 1)) + f_{j+1})\)。时间复杂度是\(O(n^2)\),无法接受。
发现\(i\)\(j\)是互不影响的,也就是可以看作\(f_{j+1} + sum_j - j - sum_{i+1} + i + 1\),那么希望\(f_{j+1} + sum_j - j\)最小,维护一个后缀最小值就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n + 1);
	for (int i = 1; i <= n; ++ i) {
		std::cin >> a[i];
	}
    std::vector<i64> sum(n + 1);
    for (int i = 1; i <= n; ++ i) {
        sum[i] = sum[i - 1] + a[i];
    }

    constexpr i64 inf = 1e18;
    std::vector<i64> f(n + 2, inf);
    f[n + 1] = 0;
    f[n] = a[n];
    i64 min = sum[n] - n;
    for (int i = n - 1; i >= 1; -- i) {
        f[i] = a[i] + std::max(0, a[i + 1] - i) + std::min(f[i + 2], min - sum[i + 1] + i + 1);
        min = std::min(min, f[i + 1] + sum[i] - i);
    }
    std::cout << f[1] << "\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;
}

E. I Yearned For The Mines

题意:一棵树,有一个人初始在树上某个节点上。你有两种操作,一种是查询一个点,如果这个人在这个点上,你就赢了;另一种是把\(x\)的边都断开。每次操作后另一个人可能会沿着边移动,但如果你是查询操作,则一定不会移动到你刚才查询的点上。最多\(\lfloor \frac{5n}{4} \rfloor\)次操作,求方案。

如果是一条链的话,显然可以从一端一直查询到另一端。这样一定可以找到。
那么我们把树分为若干条链,考虑给树染色,记颜色为\(1, 2,3\)。颜色为\(3\)的就是我们要断开的点,颜色为\(1\)表示在一条链上,颜色为\(2\)表示该点把两条链连接起来了。
那么\(dfs\)中遍历每个点的子节点,不用管颜色为\(3\)的,如果没有颜色为\(2\)的点,且颜色为\(1\)的点小于等于\(1\)个,则该点可以接在颜色为\(1\)的子节点的链上,或者没有子节点就自己作为链的开头,所以颜色染为\(1\)
如果没有颜色为\(2\)的,且正好有两个颜色为\(1\)的子节点,则该点应该把两条链连接起来,颜色染为\(2\)
否则有\(3\)个以上颜色为\(1\)的或者有一个以上颜色为\(2\)的,那么就应该断开这个点连接的边,因为这些子节点不能合为一条链。所以颜色染为\(3\)
那么这样就把树变成了若干条链,每个点都有一次查询操作,那么断开操作不超过\(\lfloor \frac{n}{4} \rfloor\),也就是颜色为\(3\)的点不超过这个数。那么一个点被染为\(3\),则要么有三个以上颜色为\(1\)的子节点,要么有至少一个颜色为\(2\)的子节点,发现这些部分都至少有三个点,也就是最差是三个颜色非\(3\)的点产生一个颜色为\(3\)的节点。那么记颜色为\(3\)的点有\(x\)个,则有\(x + 3x \leq n\)\(x \leq \lfloor \frac{n}{4} \rfloor\)
然后就是记录方案,我是用两次\(bfs\)找链两端的点。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<std::vector<int>> adj(n);
	for (int i = 1; i < n; ++ i) {
		int u, v;
		std::cin >> u >> v;
		-- u, -- v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}

	std::vector<std::pair<int, int>> ans;

	std::vector<int> c(n);
	auto dfs = [&](auto & self, int u, int fa) -> void {
		int cnt[3]{};
		for (auto & v : adj[u]) {
			if (v == fa) {
				continue;
			}

			self(self, v, u);
			++ cnt[c[v]];
		}

		if (cnt[2] || cnt[1] >= 3) {
			c[u] = 3;
		} else if (cnt[1] == 2) {
			c[u] = 2;
		} else {
			c[u] = 1;
		}
	};

	dfs(dfs, 0, -1);

	std::vector<int> st(n);
	for (int i = 0; i < n; ++ i) {
		if (c[i] == 3) {
			ans.emplace_back(1, i);
			ans.emplace_back(2, i);
			st[i] = 1;
		}
	}

	std::vector<int> pre(n, -1), cnt(n), d(n);
	auto get = [&](int u) -> void {
		int t = 1;
		auto bfs = [&](int s) -> int {
			std::queue<int> q;
			q.push(s);
			d[s] = 0;
			cnt[s] = t;
			int res = s;
			while (q.size()) {
				int u = q.front(); q.pop();
				if (d[u] > d[res]) {
					res = u;
				}
				for (auto & v : adj[u]) {
					if (!st[v] && cnt[v] < t) {
						d[v] = d[u] + 1;
						cnt[v] = t;
						pre[v] = u;
						q.push(v);
					}
				}
			}

			return res;
		};

		int U = bfs(u);
		t = 2;
		int V = bfs(U);
		while (U != V) {
			ans.emplace_back(1, V);
			st[V] = 1;
			V = pre[V];
		}

		ans.emplace_back(1, U);
		st[U] = 1;
	};

	for (int i = 0; i < n; ++ i) {
		if (!st[i]) {
			get(i);
		}
	}

	std::cout << ans.size() << "\n";
	for (auto & [op, x] : ans) {
		std::cout << op << " " << x + 1 << "\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;
}
posted @ 2025-08-25 01:08  maburb  阅读(380)  评论(0)    收藏  举报