Educational Codeforces Round 91 (Rated for Div. 2)

Educational Codeforces Round 91 (Rated for Div. 2)

1. 题目分析

  • A:思维
  • B: 思维
  • C: 贪心
  • D: 思维+分类讨论

2. 题解

A. Three Indices

题意: 给定t个样例,每个样例给定一个n,而后给出n个数字,要求判断是否存在3个数字ai,aj,ak,满足i < j < k, 且aj > ai, aj > ak。如果存在输出YES,而后输出i, j, k;不存在输出NO。T ~ 200, n ~ 1000
题解: 先正着扫一遍,判断当前数字前最小的那个数字下标;再倒着扫一遍,判断当前数字后最大的数字的下标。而后正着扫一遍判断当前数字前是否有比它小的数字,当前数字后是否有比它大的数字即可。O(n)
代码:

#include <bits/stdc++.h>
 
using namespace std;
 
int const N = 1e3 + 10;
int a[N], n, T, le[N], ri[N];
 
int main() {
    cin >> T;
    while (T--) {
        memset(ri, -1, sizeof ri);
        memset(le, -1, sizeof le);
        cin >> n;
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        int minv1 = 1e9 + 10, minv2 = 1e9 + 10, minid1 = -1, minid2 = -1;
        for (int i = 1; i <= n; ++i) {
            if (minv1 < a[i]) le[i] = minid1;
            if (a[i] < minv1) {
                minv1 = a[i];
                minid1 = i;
            }
        }
        for (int  i = n; i >= 1; --i) {
            if (minv2 < a[i]) ri[i] = minid2;
            if (a[i] < minv2) {
                minv2 = a[i];
                minid2 = i;
            }
        }
        
        int flg = 0;
        for (int i = 2; i <= n; ++i) {
            if (le[i] != -1 && ri[i] != -1) {
                cout << "YES\n";
                cout << le[i] << " " << i << " " << ri[i] << endl;
                flg = 1;
                break;
            }
        }
        if (flg == 1) continue;
        cout << "NO\n";
    }
    return 0;
}

B. Universal Solution

题意: 现在要和机器人玩猜拳,机器人的出拳方式已经知道了。人的出拳方式需要确定,同时从什么时候出拳也需要确定,请找到一种出拳方式,使得人无论是在什么时候出拳,胜利的概率都能最大。给出t个测试样例,每个测试样例给出一个长度为n的字符串,表示机器人的出拳方式。t ~ 1000, n ~ 2e5
代码
题解: 无论是从什么时候出拳都要保证胜率最大,因此只需要统计当前机器人出拳的最多的那个是什么即可,而后每次都成胜最多的那个即可。
代码:

#include <bits/stdc++.h>
 
using namespace std;
 
int T;
 
int main() {
    int R = 0, S = 0, P = 0;
    cin >> T;
    string s;
    while (T--) {
        cin >> s;
        R = 0, S = 0, P = 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == 'R' ) R++;
            else if (s[i] == 'S') S++;
            else if (s[i] == 'P') P++;
        }
        int maxv = max(R, max(S, P));
        if (R == maxv) {
            for (int i = 0; i < s.size(); ++i) 
                cout << 'P';
        }
        else if (S == maxv) {
            for (int i = 0; i < s.size(); ++i) 
                cout << 'R';
        }
        else if (P == maxv) {
            for (int i = 0; i < s.size(); ++i) 
                cout << 'S';
        } 
        cout << endl;
    }
    return 0;
}

C. Make It Good

题意: 现在有n个同学,希望把这n个同学分成很多的非空的团队,每个团队的能力值=团队中能力值最低的同学的能力值*团队的人数,问最多能够组成多少个这样的团队。给定t个测试样例,每个测试样例给出n和k,n表示有n个同学,k表示团队的最小能力值。而后给出n个数字表示每个同学的能力值。
题解: 考虑贪心处理,先把同学按照能力值按照从小到大排序。而后从大到小扫描,如果当前同学的能力值大于k,则可以单独组成团队;如果小于k,那么考虑增加团队的人数;O(n)扫描即可
代码:

#include <bits/stdc++.h>
 
using namespace std;
 
int const N = 2e5 + 10;
int T, a[N], n, x;
 
int main() {
    cin >> T;
    while (T--) {
        cin >> n >> x;
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        int res = 0, mul = 1e9, cnt = 0;
        for (int i = n; i >= 1; --i) {
            if (a[i] >= x) {
                res ++;
                // cout << "i:" << i << endl;
                continue;
            }
            else {
               mul = a[i];
               cnt++;
               if (mul * cnt >= x) {
                   res ++;
                //   cout << "i:" << i << endl;
                   cnt = 0;
               }
            }
            
        }
        cout << res << endl;
    }
    return 0;
}

D. Berserk And Fireball

题意: 现在有两排战士,第一排战士有n个,对第一排战士进行消灭一定数目后将会变成第二排战士。消灭战士的方式有两种,第一种是选择连续k个战士,将其消灭,这将消耗x元;第二种是选择两个相邻的战士,消灭其中较小的那个战士,这将消耗y元。第一行给定n和k,第二行给定x、k和y,第三行给出第一排战士的编号,第四行给出第二排战士的标号。大于最小的花费,如果不存在输出-1.
题解: 题目给出了第二排战士,所有只需要按照第二排战士的的最后结果去匹配第一行战士,就可以得到很多的分段,然后计算是否可以删除这些分段,如果能够删除,求出最小的花费即可,需要分类讨论。
如果序列 b 中元素的出现顺序与 a 不一致,则无解。
否则根据 b 将 a 分割为一个个区间,对每一个区间进行单独操作。
对于一个长度小于 k 的区间:

  • 如果区间最大值大于两端的分割点,则无解
  • 否则花费为 size×y
    对于一个长度大于等于 k 的区间:
  1. 如果区间最大值大于两端的分割点,则必须使用一次操作一
    • 如果操作一花费较小,花费为 ⌊sizek⌋×x+size % k×y
    • 如果操作二花费较小,花费为 x+(size−k)×y
  2. 如果区间最大值小于两端的分割点,则可不必使用操作一
    • 如果操作一花费较小,花费为 ⌊sizek⌋×x+size % k×y
    • 如果操作二花费较小,花费为 size×y

代码:

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
    ll n, m, x, k, y; cin >> n >> m >> x >> k >> y;
    int a[n] = {};
    int pos[n] = {};
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        --a[i];
        pos[a[i]] = i;
    }
    int b[m] = {};
    int mx_pos = 0;
    bool skip[n] = {}; //记录在 a 中的分割点
    for (int i = 0; i < m; i++) {
        cin >> b[i];
        --b[i];
        if (pos[b[i]] < mx_pos) {
            cout << -1 << "\n";
            return 0;
        } else mx_pos = pos[b[i]];
        skip[b[i]] = true;
    }
    vector<vector<int>> v; //存储每个区间
    vector<pair<int, int>> border; //存储每个区间两端的分割点
    vector<int> t; //每个区间
    int l = -1, r = -1; //左右端点
    for (int i = 0; i < n; i++) {
        if (skip[a[i]]) { //如果遇到区间分割点
            if (l == -1 and r == -1) { //第一个区间只有右端点
                r = a[i];
            } else { //之后区间的左端点为上一个区间的右端点
                l = r;
                r = a[i];
            }
            if (t.size() > 0) {
                v.push_back(t);
                border.emplace_back(l, r);
                t.clear();
            }
            continue;
        }
        t.push_back(a[i]);
    }
    if (t.size() > 0) {
        l = r;
        v.push_back(t);
        border.emplace_back(l, -1);
        t.clear();
    }
    ll ans = 0;
    for (int i = 0; i < v.size(); i++) {
        bool seg_mx = *max_element(v[i].begin(), v[i].end()) > max(border[i].first, border[i].second);
        if (v[i].size() < k) {
            if (seg_mx) {
                cout << -1 << "\n";
                return 0;
            }
            ans += v[i].size() * y;    
        } else {
            if (seg_mx) 
                ans += min(x + (v[i].size() - k) * y, v[i].size() / k * x + v[i].size() % k * y);
            else
                ans += min(v[i].size() * y, v[i].size() / k * x + v[i].size() % k * y);
        }
    }
    cout << ans << "\n";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值