Skip to content

查看源代码:
CF2133-Minecraft.md

---
title: "CF2133: Minecraft"
createTime: 2025/8/25
categories:
    - IT
tags:
    - OI
---

## [CF2133A. Redstone?](https://codeforces.com/problemset/problem/2133/A)

不难发现
$$
\prod \frac{b_i}{b_{i+1}} = \frac{b_1}{b_n}
$$
开一个 set,找有没有相同的即可。

```cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        set<int> s;
        bool ans = false;
        
        for (int i=1; i<=n; i++) {
            int a;
            cin >> a;
            if (s.count(a)) {
                ans = true;
            }
            s.insert(a);
        }
        
        cout << (ans? "YES\n" : "NO\n");
    }
    
    return 0;
}
```

## [CF2133B. Villagers](https://codeforces.com/problemset/problem/2133/B)

样例启示我们:将 $g_i$ 两两配对,接下来将每一对合并。之后这一对较小的一个会减到 $0$,于是可以用 $\max(0,0)=0$ 的绿宝石连接每一对。对于奇数个,会多出来一个孤立的,把它和任意一个 $0$ 合并。

为什么是最优的?

**Lemma 1.** 最大的 $g_i$ 一定会计入代价。

**Lemma 2.** 若一种状态的数字完全小于另一种,则小的方案一定不劣。

**Lemma 3.** 在一个连通块内,用数字小的代替数字大的一定不劣。

综合以上三点,由 Lemma 1,从最大的开始模拟;由于 Lemma 3,之后连通块一定会变成相当于一个 $0$;而由 Lemma 2,则知道一定要把次大的变成 $0$ 最优。此时出现了一个更小的子问题,归纳即可。

证毕。

又:不开 **long long** 见祖宗。

```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+100;
int g[maxn];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i=1; i<=n; i++) {
            cin >> g[i];
        }
        
        sort(g+1, g+n+1);
        long long ans = 0;
        for (int i=n; i>=1; i-=2) {
            ans += g[i];
        }
        cout << ans << '\n';
    }
    
    return 0;
}
```

## [CF2133C. The Nether](https://codeforces.com/problemset/problem/2133/C)

很久没做过猜谜的交互题了。

一个兜底方案是每次询问 $(\{u,v\}, u)$,就能知道他们之间有没有边。但是纯粹的暴力只能过 $n=2,3$ 的情况。

先考虑如何找到最长路的长度。最容易想到的方法是对每个 $u$ 询问 $d_u =(V, u)$,然后取最大值。

如此,我们不仅找到了最长路,还找到它的起点。那么怎么确定它的方案呢?

最长路 $p_i$ 一定满足 $d_{p_i} = d_{p_{i+1}} + 1$。

如何在众多可能的 $P_i = \{v \in V \mid d_v = d_{p_{i-1}} - 1\}$ 中找到合适的?

只需使用兜底方案,把每个候选问一遍就行。

找长度和起点用了 $n$ 次询问,找路径用了 $(n - \#\operatorname{arg max} d_u)$ 次询问(起点不用找),可以过。

```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=512;
vector<int> dd[maxn];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        
        for (int i=1; i<=n; i++) {
            dd[i].clear();
        }
        
        int maxd=0, start=0;
        for (int u=1; u<=n; u++) {
            printf("? %d %d ", u, n);
            for (int j=1; j<=n; j++) {
                printf("%d ", j);
            }
            printf("\n");
            fflush(stdout);
            
            int d;
            scanf("%d", &d);
            dd[d].push_back(u);
            
            if (d > maxd) {
                maxd = d;
                start = u;
            }
        }
        
        vector<int> path = {start};
        for (int d=maxd-1; d>=1; d--) {
            for (int v: dd[d]) {
                printf("? %d 2 %d %d\n", path.back(), path.back(), v);
                fflush(stdout);
                int res;
                scanf("%d", &res);
                
                if (res == 2) {
                    path.push_back(v);
                    break;
                }
            }
        }
        
        printf("! %d ", maxd);
        for (int v: path) {
            printf("%d ", v);
        }
        printf("\n");
        fflush(stdout); 
    }
    
    return 0;
}
```

## [CF2133D. Chicken Jockey](https://codeforces.com/problemset/problem/2133/D)

需要最大化摔伤的收益。

手玩样例发现摔伤有两种策略:

1. 手打掉一个怪,让上面的怪掉下来。
2. 从最底下的怪开始打。

可以证明:所有策略经过交换步骤(不会变劣)之后都可以变成先 1 后 2 的策略。(这个很难发现,我是看了提示的)

考虑 dp,设杀死 $1 \sim i$,且 $i$ 以策略 $s \in \{1,2\}$ 杀死的最小攻击次数为 $f(i,s)$,则
$$
\begin{aligned}
f(0,2)&=0, f(0,1)=+\infty,\\
f(i,1) &= \min\{f(i-1,1)+h_i-1, f(i-1,2)+\max\{h_i-i+1,0\}\} \\
f(i,2) &= f(i-1,1) + h_i \qquad \text{(连续的 2 总是更劣)}
\end{aligned}
$$

```cpp
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=2e5+100, inf=0x3f3f3f3f3f3f3f3f;
int h[maxn], f[maxn][4];

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        for(int i=1; i<=n; i++){
            cin >> h[i];
        }
        
        f[0][1] = inf;
        f[0][2] = 0;
    
        for (int i=1; i<=n; i++) {
            f[i][1] = min(
                f[i-1][1] + h[i] - 1,
                f[i-1][2] + max(h[i]-i+1, 0ll)
            );
            f[i][2] = f[i-1][1] + h[i];
        }
        
        cout << f[n][1] << '\n';
    }
    
    return 0;
}
```

## [CF2133E. I Yearned For The Mines](https://codeforces.com/problemset/problem/2133/E)

根据提示,看看链怎么做,显然用操作 1 扫一遍就行。

于是考虑先用操作 2 把树拆成链,然后把每条链扫一遍(注意孤点也要扫)

由于所有点都要扫一遍,这意味着使用操作 2 的次数不能超过 $\dfrac{n}{4}$,考虑怎么最小化这个次数。

使用树形 dp:

- $f(u)$:删除 $u$ 之后,$u$ 所在子树成链森林的最小删除次数;
- $g(u)$:不删除 $u$,$u$ 所在子树成链森林且 $u$ 的度为 $1$ 的最小删除次数;
- $h(u)$:不删除 $u$,$u$ 所在子树成链森林且 $u$ 的度为 $2$ 的最小删除次数.

对于 $u \in \text{leaves}$
$$
\begin{aligned}
f(u) &= 1 \\
g(u) &= 0 \\
h(u) &= +\infty
\end{aligned}
$$

而对 $u$ 的所有儿子 $v_i$,按照 $g(u)-f(u)$ 升序排序之后,
$$
\begin{aligned}
f(u) &= 1 + \sum_{v \in \operatorname{son} u} \min\{f(v), g(v), h(v)\} \\
g(u) &= \min\{f(v_1), g(v_1)\} + f(v_2) + f(v_3) + \cdots \\
h(u) &= g(v_1) + g(v_2) + f(v_3) + \cdots
\end{aligned}
$$

dp 完再 dfs 一遍即可得到链。