Codeforces Global Round 10 [A - F]

Codeforces Global Round 10 [A - F]

A. Omkar and Password

题目大意

给定一个长度为\(n\)的序列\(a\),每次你可以从中选择相邻但不相等的两个元素\(a_i\),\(a_{i+1}\),将这两个数进行合并且替换为\(a_i + a_{i+1}\)。例如 \([7,4,3,7] \rightarrow [7,4+3,7]\),直到无法继续进行这样的操作,最后返回最终序列的长度。

1 <= n <= 2*10^5

1 <= ai <= 10^9

考点: greedy math *800

解题思路

通过思考我们可以发现,除非序列全为相同的数字,否则我们一定可以将最后的序列长度压缩至\(1\)

因此可以写出如下代码:

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 50;
int f[maxn];

void solve(){
    int n; cin >> n;
    for (int i = 0; i < n; ++ i) cin >> f[i];

    sort(f, f + n);
    if (f[0] == f[n - 1]) cout << n << '\n';
    else cout << "1\n";
}

int main(){
    int t; cin >> t;
    while (t--)
        solve();
    return 0;
}

B. Omkar and Infinity Clock

题目大意

给定一个长度为\(n\)的序列\(a\),对其进行\(k\)次操作,每次操作为:

  1. 取出当前序列的最大值\(d\)
  2. 对于序列中的每个数\(a_i\),将其变为\(d - a_i\)

返回进行\(k\)次操作之后序列的情况。

1 <= n <= 2*10^5

1 <= k <=10^18

考点: implementation math *800

解题思路

在进行第一次操作后,序列中的最大值和最小值已经固定,其中最大值为:\(\max{arr} - \min{arr}\),最小值为\(0\),之后再进行操作实际上是一种周期性的重复。

(小tips:在看到\(k\)的数据范围时,可以猜测这题是一个周期性问题!)

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int maxn = 2e5 + 50;
int f[maxn];
int ans[maxn][2];
int main(){
    int t; cin >> t;
    while (t--){
        int n;
        LL k;
        cin >> n >> k;

        int mn = 0x3f3f3f3f, mx = 0xc0c0c0c0;
        for (int i = 0; i < n; ++ i){
            cin >> f[i];
            mn = min(mn, f[i]);
            mx = max(mx, f[i]);
        }

        for (int i = 0; i < n; ++ i) ans[i][0] = mx - f[i];
        for (int i = 0; i < n; ++ i) ans[i][1] = (mx - mn) - ans[i][0];

        if (k & 1){
            for (int i = 0; i < n; ++ i) cout << ans[i][0] << (i == n - 1 ? '\n' : ' ');
        }else {
            for (int i = 0; i < n; ++ i) cout << ans[i][1] << (i == n - 1 ? '\n' : ' ');
        }
    }
    return 0;
}

C. Omkar and Waterslide

题目大意

给定一个长度为 \(n\) 的数组 \(a\) ,每次可以选取一个非降序列,使得序列中每个值增加一。问最少操作多少次使得整个数组 非降

(\(1 \leq n \leq 2 * 10^5\))

(\(0 \leq a_i \leq 10^9\))

greedy implementation *1200

解题思路

分析后可以发现,只需要考虑谷底值。由于要求整个序列非降,因此我们只需要考虑\(a_i\)\(a_{i - 1}\)的关系。(左值)

  • 当碰到 \(a_i \leq a_{i - 1}\) 时需要进行操作,将\(a_i \rightarrow a_{i + 1}\)
  • 当碰到 \(a_i \ge a_{i - 1}\) 时,则不需要考虑。

通俗的话来讲,每次你只需要考虑能否满足当前值\(a_i\)大于等于前一个值\(a_{i - 1}\)

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int maxn = 2e5 + 50;
int f[maxn];

void solve(){
    int n; cin >> n;
    for (int i = 0; i < n; ++ i) cin >> f[i];

    LL ans = 0;
    for (int i = 0; i + 1 < n; ++ i){
        ans += max(f[i] - f[i + 1], 0); // 只需要考虑左值
    }
    cout << ans << '\n';
}

int main(){
    int t; cin >> t;
    while (t--)
        solve();
    return 0;
}

D. Omkar and Bed Wars

题目大意

\(1\)\(n\)围成一个圈, 每个人可以选择攻击左边(L)的人,或者右边(R)的人。且需要满足如何规则:

  • 当只被一个人攻击时,必须攻击这个人。
  • 当没有被攻击或者被两个人攻击时,可以攻击身边的任意一个人。

给定一个长度为 \(n\) 的序列代表攻击情况,问最少需要修改几次序列能使其满足规则。

(\(3 \leq n \leq 2*10^5\))

考点:constructive algorithms dp greedy string suffix structures *1700

解题思路

经过分析,可以发现只要存在LLL,RRR都是不合理的。要进行替换。也就是将其中某个位置的字符改变。

之后,需要考虑如何对于这种LLL...LLL序列进行处理能使得到的结果最优。

  • RRLRR改变一个字符,最多能处理长度为 5 的重复串
  • RRLRRLRR 改变两个字符,最多处理长度为 8 的重复串

然后我们可以得出结论:对于长度为 \(n,n \ge 3\) 的重复串(注意,这里我们没有考虑首尾连接的情况),我们只需要改变 \(\frac{n}{3}\)

现在考虑首位连接的情况,当首尾连接时,长度为 \(n,n \ge 3\) 的重复串需要进行几次操作,由于首尾相连,因此首端和尾端能放置的字符从原来的 2 个,到现在的最多 1 个。也就是从RRLRR 改变为 RLR。因此我们只需要改变 \(\frac{n + 2}{3}\) 次。(逆向思维,认为我们重复串的长度为 \(n + 2\))。

在书写代码的时候,可以首先将尾部与首部相同的字符去除,并修改计数器(相当于将其转移到首部)。之后判断是否整个序列为相同字符,若整个序列相同则特殊处理。

代码

#include <bits/stdc++.h>
using namespace std;


void solve(){
    int n; cin >> n;
    string ss; cin >> ss;

    int cnt = 0, ans = 0;
    // 将尾部转移到首部(通过修改 cnt 完成)
    while (!ss.empty() && ss[0] == ss.back()){ 
        ++ cnt;
        ss.pop_back();
    }
    if (ss.empty()){
        if (cnt <= 2){
            cout << 0 << '\n';
            return;
        }
        if (cnt == 3){
            cout << 1 << '\n';
            return;
        }
        cout << (cnt + 2) / 3 << '\n';
        return;
    }

    ss += '$'; // 添加一个字符,保证能全部处理完成
    for (int i = 0; i + 1< ss.size(); ++ i){
        ++ cnt;
        if (ss[i] != ss[i + 1]){
            ans += (cnt / 3);
            cnt = 0;
        }
    }
    cout << ans << '\n';
}


int main(){
    int t; cin >> t;
    while (t--)
        solve();
    return 0;
}

后记

该题还有 dp 解法,后面补上

E. Omkar and Duck

题目大意

给定一个 \(n\) ,生成一个\(n \times n\)的矩阵。要求给定一个\(k\)值,输出唯一确定的路径 \((1, 1) \rightarrow (n, n)\) 如此。

bitmasks constructive algorithms math *2100

解题思路

新知识点补充(这题还不够熟练):

  • 面对需要确定唯一路径时,可以想到2的幂,也就是通过二进制 \(2^n\) ,来构造一个唯一确定的序列

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;



int main(){
    int n; cin >> n;
    
    vector<vector<LL> > mat(n + 1, vector<LL>(n + 1 , 0));
    for (int i = 1; i <= n; ++ i){
        for (int j = 1; j <= n; ++ j){
            if (i & 1)  cout << "0 ";
            else cout << (1LL << (i + j)) << ' ';
        }
        cout << endl;
    }

    cout.flush();
    int q; cin >> q;
    while (q--){
        LL sum; cin >> sum; 
        cout << "1 1\n";
        int row = 1, col = 1;
        for (int k = 1; k <= 2 * n - 2; ++ k){
            int cur = col + row;
            if (row & 1){
                if (sum & (1LL << (cur + 1))) ++ row;
                else ++ col;
            }else{
                if (sum & (1LL << (cur + 1))) ++ col;
                else ++ row;
            }
            cout << row << " " << col << endl;
        }
    }
    cout.flush();

    return 0;
}

F. Omkar and Landslide

题目大意

给定一个长度为 \(n\) 的升序序列 \(H\) ,任意时刻序列中存在 \(h_i + 2 \leq h_{i + 1}\) 时,发生“滑坡”,即\(h_i\) 加一, \(h_{i +1}\) 减一。且所有滑坡同时进行。请问最后序列\(H\)的最终状态。

\(1 \leq n \leq 10^6\)

\(0 \leq hi \leq h_{i+1} \leq 10^{12} \quad \forall \;i \in [1, n]\)

constructive algorithms data structures greedy math *2400

解题思路

(这部分为特殊思路:看到 \(n\) 的规模以及最终时刻这两个字眼,我心里就想到了这题是一个数学贪心构造题。后面补题的时候发现果然是的)

F题的解题核心在于得到最终状态的条件。考虑到实际上只需要判断四个点就可以模拟滑坡的过程,我们选取\(a_{i - 1}, a_{i}, a_{i + 1}, a_{i + 2}\)进行考虑。我们假设\(a_{i},a{i - 1}\)之间;\(a_{i + 1}, a_{i + 2}\)之间不会进行滑坡,意味着\(a_{i + 2} - a_{i + 1} \leq 1\)(0, 1两种状态)。设当前\(a_{i + 1} - a{i} == 2\),进行滑坡之后就会出现\(a_{i} == a_{i + 1}\)。而由于\(a_{i + 1}\)减少了一,所以\(a_{i + 2} - a_{i + 1} \ge 1\)所以,每个新的等式出现一定会破坏原有的一个等式

整个序列中最终只会出现最多一对相等的元素

我们可以通过贪心构造出一个递增的数列,将剩下未分配的值贪心分配给前几个元素。

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;

inline LL read(){
    /* 注意假如为 long long 时需要修改 */
    char c = getchar();
    LL x = 0, f = 1;
    while (!isdigit(c)) { if (c == '-') f = -1; c = getchar(); }
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c^48), c = getchar();
    return f * x;
}

const int maxn = 1e6 + 50;
LL a[maxn];
int main(){
    LL n; n = read();
    LL sum = 0;
    for (int i = 0; i < n; ++ i) a[i] = read(), sum += a[i];

    sum -= (n * (n + 1)) / 2;
    LL eve, lvf;
    eve = sum / n;
    lvf = sum % n;
    for (int i = 0; i < n; ++ i){
        cout << (i + 1) + eve + (i + 1 <= lvf) << (i == n ? '\n' : ' ');
    }

    return 0;
}

后记

这个题目的重点在于最终状态的寻找,牢记贪心算法总是与数学规律挂钩(尤其是构造)

posted @ 2020-08-24 01:17  Last_Whisper  阅读(243)  评论(0编辑  收藏  举报