Practice.

A. Select Three Sticks

题意

给定 \(n\) 根火柴,定义操作为选定一根火柴,并将其长度加 \(1\) 或减 \(1\)。输出最小的操作数,使得所有火柴中有至少三根火柴的长度相等。

思路

很显然,我们只需将火柴按长短升序排序,然后枚举所有相邻的三元组,找出 将 左右两个元素 加减 到 与中间的元素相同 所需的操作数 的最小值 即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 110, inf = 0x3f3f3f3f3f3f3f3f;

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin >> a[i];
    sort(a.begin(), a.end());
    int ans = inf;
    for(int i=0;i<n-2;i++){
        ans = min(ans, a[i + 2] - a[i]);
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

就乱猜(

B. Bright, Nice, Brilliant

题意

给定一个 \(n\) 层的金字塔,第 \(i\) 层有 \(i\) 个房间,每个房间可选择是否点亮,点亮可以将该房间以及其 "子树" 的亮度 \(+1\),如下图:

a9fd5476339774d97adbfaa49ec4b4d9990da6d9

现在,给定金字塔的层数,输出每个房间是否点亮 \((0/1)\),满足所有房间的亮度相同,且点亮的房间数最大。

思路

显然,我们只能按照下面的方法构造:

\(\ \ \ \ \ \ \ \ 1 \\\ \ \ \ \ \ 1\ \ 1\\ \ \ \ \ 1\ \ 0\ \ 1\\\ \ 1\ \ 0\ \ 0\ \ 1\\ 1 \ \ 0\ ...\ 0\ \ 1\)

否则,中间一定会越来越大。

时间复杂度:\(O(n ^ 2)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 110, inf = 0x3f3f3f3f3f3f3f3f;

void solve(){
    int n;
    cin >> n;
    cout << 1 << '\n';
    for(int i=2;i<=n;i++){
        cout << 1 << ' ';
        for(int j=0;j<i-2;j++) cout << 0 << ' ';
        cout << 1 << '\n';
    }
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

别问,问就是猜的

C. Removing Smallest Multiples

题意

给定一个长为 \(n\) 的二进制字符串,代表 \(1 - n\) 中的数字是否在序列中。

对于上述字符串描述的序列,定义操作为选择任意一个 \(k \in [1, n]\),将序列中最小的 \(k\) 的倍数删去,代价为 \(k\)

输出最小的代价和,将 \(n\) 的排列变为该序列。

思路

显然,一个数被他的最小因数删去是代价最小的,但我们也需要保证 比这个数更小的 它的最小因数的倍数 不在序列中,否则我们会优先删去不可以删去的元素。

所以我们可以从小到大枚举 \(k\)

对于 \(k\),我们枚举 \(k\) 的所有倍数(包括 \(k\) 本身),并将所有倍数的答案和 \(k\) 取最小值。

在枚举倍数的时候,只要这个倍数在字符串中对应位置为 \(1\),那么我们直接结束枚举,因为我们会删除不必要的元素。

最后,我们直接遍历每个数的代价,求和即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 110, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    s = " " + s;
    vector<int> ans(n + 1, inf);
    int res = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j += i) {
            if (s[j] == '1') break;
            ans[j] = min(ans[j], i);
        }
        if (ans[i] != inf) res += ans[i];
    }
    cout << res << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

挺快的(

D. Slime Escape

题意

给定 \(n\) 个史莱姆,每个史莱姆有对应的血量,血量可以为负。给定目标史莱姆的位置,满足该史莱姆的血量为正。

规定目标史莱姆可以向左右任意移动,但每次只能移动一格,移动到对应格时会吸收该格的史莱姆(不可以重复吸收),吸收后,目标史莱姆的血量将会加上被吸收的史莱姆的血量(吸收负血量史莱姆将会扣除目标史莱姆的血量)。

输出目标史莱姆是否可以以非负的血量来到左端点或右端点。

思路

我们用双指针 \(l, r\) 表征目标史莱姆拓展的区域,显然当 \(l \leq 0\)\(r \geq n\) 时到达了端点。

那么,我们只需模拟史莱姆的移动即可,我们默认向左走,那么如果不可以向左,我们就尝试向右,不能移动就结束。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 110, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 2), pre(n + 2);
    for(int i=1;i<=n;i++) cin >> a[i], pre[i] = pre[i - 1] + a[i];
    int l = k - 1, r = k;
    int l_pre = pre[l], r_pre = pre[r];
    while(l > 0 && r < n){
        if(r_pre - pre[l - 1] >= 0) l_pre = min(l_pre, pre[-- l]);
        else if(pre[r + 1] - l_pre >= 0) r_pre = max(r_pre, pre[++ r]);
        else break;
    }
    cout << (l > 0 && r < n ? "NO\n" : "YES\n");
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

这就A了?