Codeforces Round 1040 (Div. 2) A - D题详细题解

本文为Codeforces Round 1040 (Div. 2) A - D题的详细题解, 觉得有帮助或者写的不错可以点个赞!

目录

题目A:

题目大意:

解题思路:

代码(C++):

题目B:

题目大意:

解题思路:

代码(C++):

题目C:

题目大意:

解题思路:

代码(C++):

题目D:

题目大意:

解题思路:

代码(C++):


题目A:

Problem - A - Codeforces

题目大意:

现在定义,a是数组,sum(a)即为数组a中所有元素的和。

mex(a)即为数组a的最小排除数,也就是不在数组a中的最小非负整数

现在给出你一个数组S,并且刚开始你的得分为0。

每次操作你可以从数组中取出一个一部分元素(也就是原数组会少一部分元素),把这部分元素组成的数组设置成b。

你可以对你的分数加上sum(b)或者mex(b)。

在你进行任意次操作后,求出得分的最大值。

解题思路:

感性的分析,取出来的数字要使得mex值很大,那肯定要求很苛刻,要包含从0开始连续的数字才行。

理性的分析,

mex{0} > sum{0}

mex{0, 1} > sum{0, 1}

mex{0, 1,  2} == sum{0, 1, 2}

mex{0, 1, 2, 3} < sum(0, 1, 2, 3)

很容易分析出,接下来的mex都是小于sum的

也就是得出结论,数组中有0和1的时候,取出0,1,并且把分数加上mex。

其他时候再加上sum。

更深层次的思考,mex{1} < sum{1}, mex{0} > sum{0}。

那么得出最终结论:

我们把数组中的每一个0单独取出来,加上mex{0},也就是1,也就是0的个数。

然后把其他的数字取出来,直接加到分数上。

代码(C++):

void solve() {
    int n;
    std::cin >> n;

    int sum = 0, cnt0 = 0;
    for (int i = 0; i < n; i++) {
        int x;
        std::cin >> x;
        //求出这些数字的总和
        sum += x;
        //0可以单独拿出来作为一个集合{0},MEX{0} = 1
        //所以只需要求出1的数量即可
        if (x == 0) {
            cnt0++;
        }
    }

    std::cout << sum + cnt0 << "\n";
}

题目B:

Problem - B - Codeforces

题目大意:

现在给你一个数组,里面只包含元素0, 1, 2, 并且保证0,1,2的三个数字的数量至少为1。

现在玩家A,要从数组的第一个元素开始,走到最后一个元素,每次可以往左或者往右走一格。

每次经过的元素都会加在自己的得分上(每一个元素都可以重复的计算),玩家A的目标是在走到最后一个元素上并且加到那个元素的值后,得分恰好为S (不能超过也不能小于S)。

现在初始数组为a。

现在你是玩家B,你需要重新打乱数组,使得玩家A怎么都不能得到分数S。

输出打乱后的数组,如果玩家A怎么都能得到分数S,输出-1.

解题思路:

我们很容易注意到,玩家A直接从头走到尾,会加上数组a的所有元素,我们记作tot。

也就是说,玩家A能获得的分数最小值为tot,那么S < tot的时候,肯定是无法恰好获得S分的,此时怎么排列数组都可以。

当S == tot的时候,一遍走过去就恰好达到分数S了,此时无论怎么排列数组,都会产生S分。

当S > tot的时候,此时玩家A就可以通过”刷分“操作来获取更多的得分。

我们进行如下分析:

由于数组中只存在0, 1, 2。并且保证0,1,2的三个数字的数量至少为1。

当数组中有01相邻的时候,“左右横跳”,可以获得任意的得分。

从上面的结论可以知道,在S  > tot的前提下,如果数组中有0,1相邻,玩家A肯定能获得恰好S分。

当数组中有02相邻的时候,每次左右横跳,都会获得得分2。

当数组中有11相邻的时候,每次左右横跳,都会获得得分3。

当数组中有12相邻的时候,每次左右很跳,都会获得得分4。

根据上述分析,我们现在把0和1分开摆放,可以类似于:

{0, 0, ..., 0, 2, 2, ..., 2, 1, ... ,1},现在S > tot。

我们再进行如下的分析:

当S == tot + 1,玩家A无论怎么操作都得不到S。

当S == tot + 2,玩家A可以通过02横跳来得到。

当S == tot + 3,  11横跳。

当S == tot + 4,  21横跳。

...

也就是只有在S == tot + 1的时候,玩家A怎么操作都得不到S。

综合上述推理我们即可实现代码。

代码(C++):

void solve() {
    int n, s;
    std::cin >> n >> s;

    //分别计算出0,1,2的数量
    int cnt0 = 0, cnt1 = 0, cnt2 = 0;
    for (int i = 0; i < n; i++) {
        int x;
        std::cin >> x;
        if (x == 0) {
            cnt0++;
        } else if (x == 1) {
            cnt1++;
        } else {
            cnt2++;
        }
    }
    //根据数组中1,2的数量计算数组元素的总和
    int tot = cnt1 + cnt2 * 2;

    /*
    此部分分析看题解
    */
    if (s < tot || s == tot + 1) {
        for (int i = 0; i < cnt0; i++) {
            std::cout << "0 ";
        }
        for (int i = 0; i < cnt2; i++) {
            std::cout << "2 ";
        }
        for (int i = 0; i < cnt1; i++) {
            std::cout << "1 ";
        }
        std::cout << "\n";
    } else {
        std::cout << "-1\n";
    }
}

题目C:

Problem - C - Codeforces

题目大意:

现在有m个数对(ai, bi),我们定义集合S = {(a1, b1), (a2, b2), (a3, b3), ...,(am, bm)}。

现在定义两个值f(S)和g(S)。

1.把每个数对中的(a,b)表示一个区间[a, b]。

f(S)为集合S所包含的所有区间的并集长度

2.现在(a, b)为图中的一条无向边。

g(S)是一个长度至少为3的环的节点数量。

现在给你n个数对(a, b),你需要从中取一部分数对组成集合S,你需要使得

f(S) - g(S)尽可能的大,输出此时你取的集合大小和集合里面包含的元素(原来数对的索引)。

解题思路:

感性的思考,定义t = f(S) - g(S),要使得t越来越大,那么我们需要使得f(S)越大,g(S)越小。

取更多的数对只会让f(S)越大,但是如果出现环的话,g(S)会瞬间增大很多。

也就是说,我们尽可能的取更多的数对,但是尽可能的不让g(S)出现环,此时能让t尽可能的最大。

理想分析:对于一些数对,[x1, x2], [x2, x3], [x3, x4]...[x(k - 1), x(k)],他们两两相连组成了一个环,也就是x1 == xk。

很容易知道这段区间的并集大小就是{x1, x2, ..., xk}中的最大值减去最小值, 因为两两相连,不用考虑空的部分。

我们如果删去其中的一个点[x1, x2],那么环就不存在了, 但是很容易发现并集大小是不变的。

所以,我们可以无脑的删除所有可以连成环的点,这样就可以保证最终的值是最大的!

可以用并查集快速判断是否成环。实际操作看具体代码

代码(C++):

//https://leetcode.cn/discuss/post/3583665/fen-xiang-gun-ti-dan-chang-yong-shu-ju-j-bvmv/
class UnionFind {
    std::vector<int> fa;
    std::vector<int> sz;
public:
    int cc;
    UnionFind(int n) : fa(n), sz(n, 1), cc(n) {
        std::iota(fa.begin(), fa.end(), 0);
    }
    int find(int x) {
        if (fa[x] != x) {
            fa[x] = find(fa[x]);
        }
        return fa[x];
    }

    bool is_same(int x, int y) {
        return find(x) == find(y);
    }

    bool merge(int from, int to) {
        int x = find(from), y = find(to);
        if (x == y) {
            return false;
        }
        fa[x] = y;
        sz[y] += sz[x];

        cc--;
        return true;
    }
    int get_size(int x) {
        return sz[find(x)];
    }
};


void solve() {
    int n;
    std::cin >> n;

    std::vector<std::pair<int, int>> a(n);
    for (int i = 0; i < n; i++) {
        std::cin >> a[i].first >> a[i].second;
    }

    int mx = 2 * n;
    UnionFind uf(mx + 1);
    std::vector<int> ans;
    for (int i = 0; i < n; i++) {
        int u = a[i].first;
        int v = a[i].second;
        if (uf.merge(u, v)) {
            ans.push_back(i + 1);
        }
    }

    std::cout << ans.size() << "\n";
    for (int x : ans) {
        std::cout << x << " ";
    }
    std::cout << "\n";
}

题目D:

Problem - D - Codeforces

题目大意:

现在有一个长度为n的排列P。

你需要构造一个长度为n的数组a,对于每一个ai,

你可以选择ai = pi 或者 ai = 2 * n - pi。

你的目的是使得数组a中的逆序对数目最少,输出这个最小数目。

解题思路:施工中

代码(C++):

//https://leetcode.cn/discuss/post/3583665/fen-xiang-gun-ti-dan-chang-yong-shu-ju-j-bvmv/
template<typename T>
class FenwickTree {
    std::vector<T> tree;

public:
    FenwickTree(int n) : tree(n + 1, 0) {}
    void update(int i, T val) {
        for (; i < tree.size(); i += i & -i) {
            tree[i] += val;
        }
    }
    T pre(int i) const {
        T res = 0;
        for (; i > 0; i &= i - 1) {
            res += tree[i];
        }
        return res;
    }
    T query(int l, int r) const {
        if (r < l) {
            return 0;
        }
        return pre(r) - pre(l - 1);
    }
    T total(int n) const {
        return pre(n);
    }
};

void solve() {
    int n;
    std::cin >> n;

    std::vector<int> p(n + 1);
    for (int i = 1; i <= n; i++) {
        std::cin >> p[i];
    }

    std::vector<int> l(n + 1), r(n + 1);
    FenwickTree<int> bit(n);

    i64 base = 0;
    for (int i = 1; i <= n; i++) {
        int x = i - 1 - bit.pre(p[i]);
        l[i] = x;
        base += x;
        bit.update(p[i], 1);
    }

    bit = FenwickTree<int> (n);
    for (int i = n; i >= 1; i--) {
        int x = bit.total(n) - bit.pre(p[i]);
        r[i] = x;
        bit.update(p[i], 1);
    }

    i64 ans = base;
    for (int i = 1; i <= n; ++i) {
        int x = r[i] - l[i];
        if (x < 0) {
            ans += x;
        }
    }
    std::cout << ans << '\n';
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值