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