CF Round 822 Div2 题解

比赛链接

A题 Select Three Sticks(签到)

给定 \(n\) 根木棒,第 \(i\) 根木棒的长度为 \(a_i\)。现在我们可以进行操作,每次操作选定一根木棒,将其长度增高或减少 1。问至少需要进行多少次操作,才能使得我们能够选出 3 根木棒,并拼成一个等边三角形?

\(T\leq 100,n\leq 300,1\leq a_i\leq 10^9\)

显然我们只会对这三根木棒进行操作,所以我们只需要枚举这三根木棒,就能 \(O(1)\) 的检查需要多少次操作(看最终长度变成啥就行,比赛后发现变成中间长度那个是最优的)。

直接 \(O(n^3)\) 枚举肯定不行(虽然我觉得 A 题应该把这个放过去),所以我们考虑优化:显然,排序后只需要取相邻三个即可(不相邻的肯定比相邻的劣),这样就把复杂度变成 \(O(n\log n)\) 了。

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
int solve() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    sort(a + 1, a + n + 1);
    int res = 2e9;
    for (int i = 3; i <= n; ++i) {
        int x = a[i - 2], y = a[i];
        res = min(res, y - x);
    }
    return res;
}
int main() {
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

B题 Bright, Nice, Brilliant(构造)

建议直接看原题。

(看了题解后)发现,第 \(i\) 层的亮度至多为 \(i\)(就算把前 \(i-1\) 层的灯都点上,\((i,1)\) 收到的亮度也就是 \(i-1\),加上自己的灯就是 \(i\))。

观察样例,发现一种构造方案:只点亮最两边的房间,投影法发现这样可以符合要求。

#include<bits/stdc++.h>
using namespace std;
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i)
        if (i == 1) puts("1");
        else {
            printf("1");
            for (int j = 2; j < i; ++j) printf(" 0");
            puts(" 1");
        }
}
int main() {
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

C题 Removing Smallest Multiples(贪心,数学)

给定集合 \(S=\{1,2,3,\cdots,n\}\),现在我们可以进行多次操作,每次操作选定一个正整数 \(k\),然后删除 \(S\) 里面的最小的 \(k\) 的倍数(包括 \(k\) 自己)。问至少需要删多少次,才能使得 \(S\) 变成集合 \(T\) ?(\(T\) 是给定的一个 \(S\) 的子集)

\(T\leq 10^4,1\leq n,\sum n\leq 10^6\)

要删掉某个数,应该选一个尽可能小的因数,并且这个因数的相对较小的倍数(比这个数小的)都要删。

那么我们直接贪心(或者说有点类似素数筛),从小到大枚举 \(x\),看 \(x\) 能删掉哪些数(碰到一个不能删的就 break 掉,否则判断这个数是不是之前被删掉,没被删掉的话就用 \(x\) 来删就行了),复杂度大概最多 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1000010;
int n, a[N];
char s[N];
LL solve() {
    scanf("%d%s", &n, s + 1);
    memset(a, 0, sizeof(int) * (n + 1));
    LL ans = 0;
    for (int i = 1; i <= n; ++i)
        if (s[i] == '0') {
            for (int j = i; j <= n; j += i)
                if (s[j] == '0') {
                    if (a[j] == 0) ans += i, a[j] = 1;
                }
                else break;
        }
    return ans;
}
int main() {
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

D题 Slime Escape(贪心)

给定一个数轴,位置下标从 1 到 n,第 \(i\) 个位置有一个血量为 \(a_i\) 的史莱姆。现在我们要控制位置在 \(k\) 处的史莱姆,通过左右跳动的方式到达位置 \(0\) 或者 \(n+1\)

我们的史莱姆可以左右跳动(每次一格),当目标位置有一个史莱姆的时候就吃掉它,然后自身的血量就加上它的血量。但是注意,有的史莱姆的血量是负数,这意味着吃掉它后我们的血量会变少。当血量变成负数时,我们的史莱姆就 G 了,我们不希望这种情况发生。

问:我们能否到达目标位置?

\(T\leq 2*10^4,3\leq n,\sum n\leq 2*10^5,-10^9\leq a_i\leq 10^9,a_k\geq 0\)

有这样一种思路:我们首先判断能不能向右到达终点,达不到的话就向右取一个最大子段和,吃完后看看能不能向左到达终点,达不到的话就再向左取一个最大字段和,然后再向右,持续下去,直到到达终点。至于无解,就这样判断:发现当前状态下,无论是向左还是向右都无法到达终点,且最大子段和也不再存在(不可能通过吃一段史莱姆的方式来增加血量了),这时输出无解即可。

这样反复走的时间似乎有点高,但实际上它是 \(O(n)\) 的:因为我们记录一下已经吃掉的史莱姆的范围,每次只从这个范围的左右端点扩展,类似两个指针往外拓,每个史莱姆至多吃一次,所以总复杂度是 \(O(n)\) 的。

思路其实还好,主要是写起来烦,还要记录状态啥的。

#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
int n, k; LL a[N];
struct Node { int farPos, vpos; LL x; };
Node getR(LL x, int p) {
    LL s, maxv; int farPos, vpos;
    s = x, farPos = n;
    for (int i = p + 1; i <= n; ++i)
        if ((s += a[i]) < 0) { farPos = i - 1; break; }
    maxv = x, vpos = p, s = x;
    for (int i = p + 1; i <= farPos; ++i)
        if ((s += a[i]) > maxv) maxv = s, vpos = i;
    return (Node){farPos, vpos, maxv};
}
Node getL(LL x, int p) {
    LL s, maxv; int farPos, vpos;
    s = x, farPos = 1;
    for (int i = p - 1; i >= 1; --i)
        if ((s += a[i]) < 0) { farPos = i + 1; break; }
    maxv = x, vpos = p, s = x;
    for (int i = p - 1; i >= farPos; --i)
        if ((s += a[i]) > maxv) maxv = s, vpos = i;
    return (Node){farPos, vpos, maxv};
}
//
bool solve() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
    int l = k, r = k; LL x = a[k];
    while (true) {
        Node R = getR(x, r); x = R.x;
        if (R.farPos == n) return true;
        Node L = getL(x, l); x = L.x;
        if (L.farPos == 1) return true;
        if (l == L.vpos && r == R.vpos) return false;
        l = L.vpos, r = R.vpos;
    }
    return true;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) puts(solve() ? "YES" : "NO");
    return 0;
}
posted @ 2022-09-24 13:55  cyhforlight  阅读(32)  评论(0编辑  收藏  举报