zoukankan      html  css  js  c++  java
  • Codeforces Round #673 (Div. 2)[A-E]

    Codeforces Round #673 (Div. 2)[A-E]

    A. Copy-paste

    题目大意

    给定一个长为 (n) 的数组 (a) ,你可以对任意 $ i, j ;(i eq j)$ ,进行操作 (a_j=a_i+a_j)。同时,需要满足数组中任意一项值不超过 (k),求最多可进行多少次操作。

    *800 greedy

    思路分析

    这题,显然是一个离线贪心。不了解的可以查看我的贪心算法专题、

    排序之后,把最小值不断赋值给其他数直到无法继续赋值。

    对于这种题,毫无疑问贪心算法或者数学法会是更好的选择。

    代码

    const int maxn = 1e5 + 50;
    int f[maxn], mn, pos;
    void solve(){
        int n = read(), k = read();
        for (int i = 0; i < n; ++ i) f[i] = read();
    
        sort(f, f + n);
        LL sum(0);
        for (int i = 1; i < n; ++ i) sum += (k - f[i]) / f[0];
        cout << sum << '
    ';
    }
    

    B. Two Arrays

    题目大意

    给定一个长为 (n) 的非负整数序列 (a) ,和一个定值 (T)

    定义 (f(b)) 为:

    序列 (b) 中,满足 (b_i + b_j = T) 的点对 ((i, j) ; i < j) 的总数。

    现在,要求对序列 (a) 进行黑白染色,使其分为两个子序列 (c, d) 。求问使得 (f(c) + f(d)) 最小的染色方案。

    *1100

    思路分析

    本题具有两个思路,分别为 基于数学推演的情况讨论法基于贪心算法的暴力比较法

    假如在竞赛过程中,如果能大致保证贪心算法的正确性时,用贪心算法显然更好一些。

    这里主要介绍贪心算法,数学推演实际上是将小于 (dfrac{T}{2}) 的分为一组, 大于 (dfrac{T}{2}) 的分为一组。等于 (dfrac{T}{2}) 的在两子序列中周期摆动安放。

    而贪心算法则是维护两个子序列的 hashmap 。当要插入一个数字时,比较其在两个子序列中分别能组成多少组合法点对,贪心选择数量较少的进行插入,满足要求。

    在看到题面的时候,通过注意到 (b_i + b_j = T) ,我们应该快速联想到 hashmap 解决此类问题。

    在看到最小时,应该快速思考使用在线 or 离线贪心算法。

    代码

    const int maxn = 1e5 + 50;
    int f[maxn], ans[maxn];
    void solve(){
        int n = read(), T = read();
        for (int i = 0; i < n; ++ i) f[i] = read();
    
        unordered_map<int, int> mp1, mp2;
        mp1.clear(), mp2.clear();
    
        VI ans(n);
        for (int i = 0; i < n; ++ i){
            if (mp1[T - f[i]] <= mp2[T - f[i]]){
                ans[i] = 0;
                ++ mp1[f[i]];
            }else {
                ans[i] = 1;
                ++ mp2[f[i]];
            }
        }
        show(ans);
    }
    

    C. k-Amazing Numbers

    题目大意

    给定一个长为 (n) 的序列 (a),该序列元素大小均在 ([1, n]) 中。定义 k惊喜数字

    • 在序列 (a) 所有长为 (k) 的连续子序列中均出现过的最小元素,若不存在则赋值为 (-1)

    请你计算并输出 (k in [1, n]) 的所有 k惊喜数字

    1 <= n <= 3e5

    *1500

    思路分析

    刚拿到这题时,我没有一点思路。一直在思考是不是滑动窗口,以及如何复用其中的信息,均发现无法快速解决。

    后面转换角度从每个元素的特性出发,插入头尾两个虚拟结点,寻找各元素之间的最大间隔。并发现这个方法可行。

    最终得出如下结论:

    • 假如你不自觉的想入一个问题且在一定时间内不能得出答案时,说明你有一个细节或者角度不对。
    • 如果不自觉的用宏观解决问题时(比如在段,区间),专注于微观可能是这个题目的解决方案(位,各元素之间)

    之所以写上面这段话,是因为这题当时险些没有做出,而且不属于方法不清楚的层面,而是我在思考问题时出现了角度的偏差。谨以此警示。

    回归这个题目,如上文所提,我们维护每个元素的最大间隔。易知,当 (k) 大于某元素最大间隔时,就可能对k惊喜数字产生贡献。在利用 std::map 维护每元组的间隔时,由于其自动排序的特性,可以从头至尾进行处理。

    代码

    const int maxn = 3e5 + 50;
    int f[maxn];
    void solve(){
        int n = read(), tot = 0, cnt(0);
        map<int, int> dis; // 最大间距
        unordered_map<int, int> pre; // 上一个元素出现位置
        for (int i = 1; i <= n; ++ i){ // index from 1 (in order to insert 0-index node)
            f[i] = read();
            if (pre[f[i]] == 0){
                pre[f[i]] = i;
                dis[f[i]] = i;
            }else {
                int dlt = i - pre[f[i]]; // 间距
                // wprint(dlt, dis[f[i]]);
                pre[f[i]] = i;
                if (dlt > dis[f[i]]) dis[f[i]] = dlt;
            }
        }
        for (auto &e: pre){
            int dlt = n + 1 - e.second; // insert (n+1)-index node 
            if (dlt > dis[e.first]) dis[e.first] = dlt;
            // wprint(dlt, e.first, e.second);
        }
    
        vector<int> ans(n + 1, -1);
        int stop = n + 1;
        for (auto &e: dis){ // 利用 map 的排序特性,从头至尾处理即可
            if (e.second >= stop) continue;
            for (int i = e.second; i < stop; ++ i) ans[i] = e.first;
            stop = e.second;
        }
        for (int i = 1; i <= n; ++ i) cout << ans[i] << (i == n ? '
    ' : ' ');
    }
    

    D. Make Them Equal

    题目大意

    • 出一个序列 (a),求出一个长度不超过 (3n) 的操作序列,每次操作之后序列中所有元素必须为非负整数,操作完成后使序列 (a) 中每个元素相等。
    • 定义一次操作为:选出 ((i,j,x)) 三元组,满足 (i,j) 为序列合法下标,(x)(10^9) 以内非负整数,令 (a_i:= a_i-xcdot i,a_j:=a_j+xcdot i)
    • 输出时先输出操作次数 (k),然后输出 (k) 行操作序列。

    *2000

    思路分析

    *2000 分的构造题,实际上比完赛之后想想还是很套路的。以后有机会总结一个 你只能用最多 (c cdot n) 次操作,完成一个目标

    在这里进行一个简单的总结。

    用 c * n 次操作(询问)完成目标

    首先,不管可用操作(询问)数为多少,一个永远可以尝试的思路是: 以某个最特殊的元组做跳板

    • (c == 1),则为线性操作类似冒泡的感觉,从头至尾线性操作,每次保留其中之一在下一次进行操作。
    • (c == 2),一般来说:
      • 可能为 (c == 1)时的情况,但是每次操作需要两步C. Chocolate Bunny 类似这题。
      • 或者对每个元素用一次操作将其转移到跳板上,再进行一次操作完成目的。
    • (c==3),则想法与上述差不多。

    总而言之,需要发现操作的特定性质,如互相操作跳板操作

    对于这题,比较困难的地方在于每次操作之后保证所有的元素非负。根据上述结论显然我们需要选择一共“跳板

    ”,毫无疑问这个跳板选择初始位置 (1) 最为合适。因此,我们大致制定好了构造策略:

    1. 首先将其他位置的元素转移到“跳板上”。
    2. 在借助跳板分配元素,使得每个元素相等。

    根据题设限制,每个元素最多转移 (a_i - (a_i) \%i) ,对于余下的 (a_i \% i) ,我们可以先补充上 (i - (a_i \% i)),再进行转移。经过思考,这个方案是满足条件的,实际上他也符合一种贪心的思想。我们尽可能避免对于元素进行较大的减操作

    因此最终策略为:

    • 首先,将元素转移到跳板上:
      • 先将元素补齐至 ((a_i + Delta) \% == 0)
      • 将元素转移到跳板上
    • 通过跳板逐步分配元素。

    代码

    const int maxn = 1e4 + 50;
    int f[maxn];
    void solve(){
        int tot(0), n = read();
        for (int i = 1; i <= n; ++ i) f[i] = read(), tot += f[i];
    
        if (tot % n != 0){ cout << "-1
    "; return; } // 不能整除显然不行
    
        int eve = tot / n;
        vector< tuple<int, int, int> > res;
        for (int i = 2; i <= n; ++ i){
            if (f[i] % i == 0) res.pb({i, 1, f[i] / i});
            else {
                res.pb({1, i, i - (f[i] % i)});
                res.pb({i, 1, (f[i] / i) + 1});
            }
        }
        for (int i = 2; i <= n; ++ i) res.pb({1, i, eve});
        
        wprint(sz(res));
        for (auto &e: res) wprint(get<0>(e), get<1>(e), get<2>(e));
    }
    

    E. XOR Inverse

    题目大意

    给定长度为 (n) ((1le nle3 imes 10^5)) 的数列 ({a_n}(0le a_nle 10^9)),请求出最小的整数 (x) 使 ({a_noplus x})逆序对数最少,其中$ oplus$ 是异或。

    *2000 divide and conquer CDQ分治

    思路分析

    本题最开始会比较容易想到通过 dp 去解决。考虑到逆序对这个经典的问题,便考虑到分治的思路。实际上 bit 的 0 or 1 可以类比为二叉树的左右孩子。若越靠近根结点代表的 bit 越高,则右孩子一定大于左孩子(用了字典树的知识)。

    因此,题目便比较简单了:运用 CDQ 分治的思路:

    首先,我们维护一个 dp 数组,dp[bit][i]:= x 的第 bit 位为 i 时逆序对的个数

    因为,两个元素比较大小等价于比较两者第一个不同位。不同位为 1 的更大。因此在解题的过程中,维护两个数组,左边代表当前分治到左边的元素(bit is 0),右边代表当前分治到右边的元素(bit is 1) 。因此,右边一定大于左边,在利用求解逆序对的知识,通过双指针处理两个子树合并出现的逆序对个数。

    • 若不修改该 bit (等价于 x 的第 bit 位为 0 ),则逆序对为直接求得的。
    • 若修改该bit (等价于 x 的第 bit 位为 1 ),则逆序对数为总数-不修改时的逆序对个数

    之后,贪心选取其中每 bit 逆序对较小的构造答案。

    代码

    #define pb push_back
    using VI = vector<int>;
    using LL = long long;
    
    // CDQ 分治处理点对问题
    int dp[35][2];
    void helper(VI cur, int bit = 30){
        if (bit < 0 or cur.empty()) return;
    
        int cnt1(0), ans1(0);
        int cnt2(0), ans2(0);
        VI right, left;
    
        for (auto &x: cur){
            if ((x >> bit) & 1){
                ans1 += cnt2; // 由于 index 从大到小,因此 cnt2 代表 index 大于当前元素且元素值小于当前元素的点对数。
                ++ cnt1;
                right.pb(x);
            }else {
                ans2 += cnt1;
                ++ cnt2;
                left.pb(x);
            }
        }
        helper(left, bit - 1), helper(right, bit - 1);
        dp[bit][0] += ans1;
        dp[bit][1] += ans2;
    } 
    
    void solve(){
        int n = read();
        VI a(n);
        for (auto &&e: a) e = read();
        reverse(all(a));
        helper(a);
        LL ans(0), res(0);
        for (int i = 0; i <= 30; ++ i){
            ans += min(dp[i][0], dp[i][1]);
            if (dp[i][1] < dp[i][0]) res |= (1 << i);
        }
        cout << ans << ' ' << res << '
    ';
    }	
    
  • 相关阅读:
    科普园地
    专家段 错误 新闻
    16进制转rgb
    图片截取0825
    域名的问题 图片显示不出来
    height cell0809
    添加银行卡
    DeviceDelegateHelper.m
    UUID
    MBProgressHUD 动画
  • 原文地址:https://www.cnblogs.com/Last--Whisper/p/13757291.html
Copyright © 2011-2022 走看看