CF Round #702 Div3 解题补题报告

官方题解

A题 Dense Array (签到)

给定一个数列 \(\{a_n\}\) ,你可以在数列中插入一些数字,问至少插多少,可以使得任意相邻两项的商(大的除小的)小于等于 \(2\)

\(2 \leq n \leq 50,1 \leq a_i \leq 50\)

显然的,每次找相邻两项,然后直接硬加(对小的那个不断乘 \(2\) )。这个数据规模很小,所以不需要任何优化,复杂度 \(O(n\log{a_i})\)

#include<bits/stdc++.h>
using namespace std;
const int N = 60;
int n, a[N];
int calc(int x, int y) {
    if (x > y) swap(x, y);
    int cnt = 0;
    while (2 * x < y) cnt++, x *= 2;
    return cnt;
}
void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    int ans = 0;
    for (int i = 1; i < n; ++i)
        ans += calc(a[i], a[i+1]);
    printf("%d\n", ans);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

B题 Balanced Remainders (分类讨论)

现在有一个长度为 \(n\) 的数列 \(\{a_n\}\) 。现在每次可以进行一次操作,将某一项加 \(1\) 。问至少需要多少次,可以使得数列中对 \(3\) 取模后为 \(0\) , \(1\) , \(2\) 的项的数量相等。

\(3\leq n \leq 30000\) ,保证 \(n\) 可以被 \(3\) 整除

直接从前到后扫一遍,然后将这三类的数量分别记为 \(c_0,c_1,c_2\)

现在,我们需要最少步数从 \((c_0,c_1,c_2)\) 变为 \((\frac{n}{3},\frac{n}{3},\frac{n}{3})\)

这题就是铁分类讨论,没啥好说的,讨论就完事了。

#include<bits/stdc++.h>
using namespace std;
const int N = 30010;
int n, a[N];
int solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    int c0 = 0, c1 = 0, c2 = 0;
    for (int i = 1; i <= n; ++i)
        switch (a[i] % 3) {
            case 0 : c0++; break;
            case 1 : c1++; break;
            case 2 : c2++; break;
        }
    c0 -= (n/3), c1 -= (n/3), c2 -= (n/3);
    int ans = 0;
    if (c0 == 0)
        if (c1 >= 0) return c1;
        else return 2 * (-c1);
    if (c0 > 0)
        c1 += c0, ans += c0, c0 = 0;
    else if (c0 < 0)
        c2 -= (-c0), ans += (-c0), c0 = 0;
    if (c1 >= 0) return ans + c1;
        else return ans + 2 * (-c1);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
        printf("%d\n", solve());
    return 0;
}

C题 Sum of Cubes (枚举优化)

给定正整数 \(x\),问是否存在正整数 \(a,b\) ,使得 \(a^3+b^3=x\)

\(1\leq x \leq 10^{12}\)

显然 \(a^3,b^3 < a^3+b^3=x \leq 10^{12}\) ,所以 \(a,b <10^4\)

直接枚举?说实话,如果只有一组数据,说不定能卡过去,但是这题有多组数据,我就不冒这个险了(逃

其实我们不妨设 \(a[i]=i^3\) ,那么问题就变成了另一个:

是否存在 \(1\leq i,j \leq 10000\),使得 \(a_i+a_j=x\) ?

这题是不是感觉很熟悉?链接

老题目了,就是通过先 \(Hash\) 一遍,以空间换时间,将复杂度从 \(O(n^2)\) 压到 \(O(n)\) 。咋哈希?直接 \(map\) 就完事了。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
map<LL, LL> Hash;
inline LL f (LL x) { return x * x * x; }
bool solve() {
    LL x;
    scanf("%lld", &x);
    for (LL a = 1; f(a) < x; ++a)
        if (Hash.find(x - f(a)) != Hash.end())
            return true;
    return false;
}

int main()
{
    for (LL i = 1; i <= 10000; ++i)
        Hash[f(i)] = i;

    int T;
    scanf("%d", &T);
    while (T--)
        puts(solve() ? "YES" : "NO");
    return 0;
}

当然,也可以直接调用 \(\text{cmath}\) 里面的函数,判断 \(x-a^3\) 是不是立方数,由于浮点误差,我就不写了(逃

D题 Permutation Transformation (递归)

给定一个大小为 \(n\) 的排列 \(\{a_n\}\) ,尝试构造一棵二叉树,具体步骤建议看原题(我懒得翻译了)

\(n \leq 100\)

递归建树,有手就行,复杂度 \(O(n\log n)\)

(如果 \(n\) 的复杂度达到 \(10^6\) 甚至 \(10^7\) 级别的话,那最好先用数据结构(例如 \(ST\) 表)预处理一下 \(RMQ\) (话说我感觉这玩意复杂度也是 \(O(n \log n)\) 的))

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N], d[N];
void build(int l, int r, int depth) {
    if (l > r) return;
    int k = 0;
    for (int i = l; i <= r; ++i)
        if (a[k] < a[i]) k = i;
    d[k] = depth;
    build(l, k - 1, depth + 1);
    build(k + 1, r, depth + 1);
}
void solve()
{
    //init
    memset(d, 0, sizeof(d));
    //input
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    //build
    build(1, n, 0);
    //output
    for (int i = 1; i < n; ++i)
        printf("%d ", d[i]);
    printf("%d\n", d[n]);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

E题 Accidental Victory (贪心)

给定 \(n\) 位玩家,每位玩家初始代币数量为 \(a_i\)

每次比赛选择两位剩下来的玩家,代币多者获胜,并且夺走败者的代币(代币数目一样则随机决定胜负)。最后场面下剩下来的人就会成为胜者。

现在我们想知道,哪些玩家可能成为胜者?

\(1\leq n \leq 2*10^5,1 \leq a_i \leq 10^9\)

有一个显然的策略:先将所有玩家按照代币数量从小到大排列,然后从前向后扫一遍即可,不停尝试吞,复杂度 \(O(n \log n)\) 。(记得开 \(\text{long long}\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n;
struct node {
    LL val;
    int id;
    bool operator < (const node &rhs) const {
        return val < rhs.val;
    }
}a[N];
int t[N];
void solve()
{
    //input
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i].val), a[i].id = i;
    //solve
    sort(a + 1, a + n + 1);
    LL ans = a[1].val, tmp = a[1].val;
    for (int i = 2; i <= n; ++i) {
        if (tmp < a[i].val) ans = a[i].val;
        tmp += a[i].val;
    }
    int cnt = 0;
    for (int i = 1; i <= n; ++i)
        if (a[i].val >= ans) t[++cnt] = a[i].id;
    sort(t + 1, t + cnt + 1);
    //output
    printf("%d\n", cnt);
    for (int i = 1; i < cnt; ++i)
        printf("%d ", t[i]);
    printf("%d\n", t[cnt]);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

F题 Equalize the Array (排序,离散化,二分)

当一个数列中,每个元素出现的次数相等,均为 \(C\) 次,那么这个数列就是完美的

给定一个长度为 \(n\) 的数列 \(\{a_n\}\) ,问至少需要去除多少元素,可以使得这个数列变成完美的

\(1\leq n \leq 2*10^5,1 \leq a_i \leq 10^9\)

不管咋样,先排个序再说吧。

很显然,我们需要统计每个元素出现的个数。考虑到这个规模,我们必须离散化或者用 \(map\)

离散化之后,不妨记 \(cnt_x\)\(x\) 出现的次数,那么,对于 \(C\) ,我们需要做到:

  1. 将出现次数不足 \(C\) 次的元素删除
  2. 将出现次数超过 \(C\) 次的元素削减到 \(C\)

那么总次数为 $\sum\limits_{cnt_x < C} cnt_x + \sum\limits_{cnt_x \geq C} (cnt_x - C) $ ,优化一下,也就是 \(\sum cnt_x - \sum\limits_{cnt_x \geq C} C\)

那么我们可以直接对 \(cnt\) 数组排序,然后每次二分即可

总复杂度 \(O(n \log n)\) ,小细节多的一

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n, a[N], m, b[N];
void discrete() {
    sort(a + 1, a + n + 1);
    m = 0;
    for (int i = 1; i <= n; ++i)
        if (i == 1 || a[i] != a[i-1])
            b[++m] = a[i];
}
int query(int x) {
    return lower_bound(b + 1, b + m + 1, x) - b;
}
int cnt[N];
LL sum[N];
void solve()
{
    //读入
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    //离散化
    discrete();
    memset(cnt, 0, sizeof(int) * (m + 1));
    for (int i = 1; i <= n; ++i)
        ++cnt[query(a[i])];
    //求解
    sort(cnt + 1, cnt + m + 1);
    cnt[m + 1] = 1e9 + 10;
    for (int i = 1; i <= m; ++i)
        sum[i] = sum[i - 1] + cnt[i];
    LL ans = 1e9 + 10;
    for (LL C = 0; C <= n; ++C) {
        int i = lower_bound(cnt + 1, cnt + m + 1 + 1, C) - cnt;
        ans = min(ans, sum[m] - (m - i + 1) * C);
    }
    printf("%lld\n", ans);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

G题 Old Floppy Drive (前缀和,单调性维护)

有点不太会翻译,告辞

不管咋样,前缀和肯定是要维护一下的。

但是吧,如果每个点上面的数都是正数,那我们直接每次二分一下就好了,并不是很难,但是现在有负数就离谱。

实际上,我们只需要用一个栈来维护单调性,只保存其中单调递增的就行了(反正减的也没用

然后在这上面二分就好了(逃

总体步骤如下:

  1. 维护前缀和 \(s\)
  2. 在前缀和里面提取单调增部分组成新数列 \(val\) ,并记录下对应 \(id\)
  3. 判定无解的情况
    • 无法组成 \(val\) 数组(全都 \(NM\) 是负数,还不停减)
    • \(x\) 大于 \(val\) 最大值,但是 \(s_n \leq 0\) ,导致最后累计和永远不可能达到 \(x\)
  4. \(x\) 大于 \(val\) 中最大值时,不断减去 \(s_n\) 并统计答案(必须用除法优化,不能不停的减)
  5. \(x\) 小于等于 \(val\) 中最大值的时候,对 \(val\) 进行二分,找出对应的坐标 \(id\) 并加上去

小细节真的多的一,这对着数据都调了大半天就离谱

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n, m;
LL a[N], s[N];
//
int cnt;
LL val[N];
int id[N];
//
LL Query(LL x) {
    if (cnt == 0 || (x > val[cnt] && s[n] <= 0)) return -1;
    LL ans = -1;
    if (x > val[cnt]) {
        LL t = (x - val[cnt] + s[n] - 1) / s[n];
        ans += n * t, x -= s[n] * t;
    }
    int k = lower_bound(val + 1, val + cnt + 1, x) - val;
    ans += id[k];
    return ans;
}
void solve()
{
    //input
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    //pre
    cnt = 0;
    for (int i = 1; i <= n; ++i) {
        s[i] = s[i - 1] + a[i];
        if (s[i] > val[cnt])
            id[++cnt] = i, val[cnt] = s[i];
    }
    //solve
    for (int i = 1; i <= m; ++i) {
        LL x;
        scanf("%lld", &x);
        printf("%lld ", Query(x));
    }
    puts("");
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}
posted @ 2021-02-20 22:13  cyhforlight  阅读(104)  评论(0编辑  收藏  举报