Codeforces Round 966 (Div. 3)-cf的第2000场比赛A-D超详细题解(C++,Python)-来自绿名预备选手的思维

前言: 

(这次比赛应该是codeforces这个网站的第2000场比赛吧,然后这次div3难度感觉像是div4?可能是我变强了(bushi),不过我还是一如既往的四题选手,不过这次四题速度挺快的,思路都会很快想出来的,很少想错,然后总排名是7000多一点,早知道注册账号后前面几把有额外加分就不这么随便了)

(system test 完,想吐槽一句,就是说都在最后几分钟提交是吧,90%到98%我名次没什么变化,然后最后2%名次掉了快1000了,本来能上绿的,现在差三分)

        本文主要是题A到题D的详细思路和解法,如果觉得有帮助或者写的还不错可以点个赞

        个人觉得D的题解写的比较详细

本次比赛题目链接:Dashboard - Codeforces Round 966 (Div. 3) - Codeforces

目录

题目A

题目B

题目C

题目D


题目A:

Problem - A - Codeforces

题目大意解析和解题思路:

题目大意就是一个人写下了一个数字,这个数字是10^x次方的形式,但是"^"这个符号忘记写了,然后就写成一串了,已知底数是10,然后指数大于2,然后判断这个数字是否可能是他写下来的数字

比如

100,不是,因为指数小于2

1004,不符合书写条件

1010,105,是的,满足条件

2033,不满足底数是10的条件

将输入的数字转换成字符串就行了,然后满足上面条件的字符串

长度必须大于等于3,然后第一位必须是1,第二位必须是0,如果长度等于三位,那第三位必须大于等于2,如果长度大于三位,那么第一位不能为0

嗯...很好理解的逻辑

代码(C++):

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
 
    int tt;
    cin >> tt;
    while (tt--) {
        string s;
        cin >> s;
        if (s.size() < 3) {
            cout << "NO\n";
            continue;
        }
        if (s[0] != '1' || s[1] != '0') {
            cout << "NO\n";
            continue;
        }
        if (s.size() == 3 && s[2] < '2' || s.size() > 3 && s[2] == '0') {
            cout << "NO\n";
            continue;
        }
        cout << "YES\n";
    }
    
    return 0;
}

代码(Python):

def main():
    t = II()
    res = []
    for _ in range(t):
        s = input()
        r = 1
        if len(s) < 3:
            res.append("NO")
            continue
        if s[0] != '1' or s[1] != '0':
            res.append("NO")
            continue
        if (len(s) == 3 and s[2] < '2') or (len(s) > 3 and s[2] == '0'):
            res.append("NO")
            continue
        res.append("YES")
    
    for r in res:
        print(r)

题目B:

Problem - B - Codeforces

题目大意解析和解题思路:

题目可以理解依次上车,座位就是一列,然后有一个规则,就是上车的人必须前面或者后面有人

都有人也行(这种情况出现的前提是前面有人违规),然后给你一个数组,从前到后依次表示第一个人到最后一个人选择的位置,判断是否都按规则来了

用哈希表模拟就行了,遍历一遍数组,然后不断加入新元素x,判断x - 1或者 x + 1是否在哈希表中即可

代码(C++):

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
 
    int tt;
    cin >> tt;
    while (tt--) {
        int n;
        cin >> n;
        unordered_map<int, int> has;
        bool f = true;
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            if (!has.empty() && has[x + 1] == 0 && has[x - 1] == 0) {
                f = false;
            }
            has[x]++;
        }
        if (f) {
            cout << "YES\n";
        } else {
            cout << "NO\n";
        }
    }
    return 0;
}

代码(Python):

def main():
    t = II()
    res = []
    for _ in range(t):
        n = II()
        arr = LII()
        has = defaultdict(int)
        f = True
        for x in arr:
            if len(has) > 0 and has[x - 1] == 0 and has[x + 1] == 0:
                f = False
            has[x] += 1
        if f:
            res.append("YES")
        else:
            res.append("NO")
    
    for r in res:
        print(r)

题目C:

Problem - C - Codeforces

题目大意解析和解题思路:

这一题挺有意思的,描述也简单

就是说给你一个数组,然后若干个字符串,数组长度和字符串长度相同

数组里面两个相同数字的两个位置要保证字符串的这两个位置也相同

不同的两个数字对于的位置,字符串这两个位置的字符也不同

比如示例1
5

3 5 2 1 3

2

abfda

afbfa

数组的下标0和5的数字相同,然后字符串必须保证0和5相同

这一点字符串1和2都满足

然后数组其他位置都不同,字符串必须保证其他位置也不同

这一点字符串2不满足,字符串2的下标1和3处的字符相同

使用两个哈希表模拟就行

map1存放数字 - 字符

map2存放字符- 数字

遍历字符串,不断存入键-对,然后碰到相同元素的时候

判断此时的键,是否对应原来的那个对

嗯..感觉描述的有点抽象,直接看代码

代码(C++):

string check(int a[], int n, string s) {
    if (n != s.size()) {
        return "NO";
    }
    
    unordered_map<int, char> map1;
    unordered_map<char, int> map2;
    
    for (int i = 0; i < n; ++i) {
        if (map1.find(a[i]) != map1.end()) {
            if (map1[a[i]] != s[i]) {
                return "NO";
            }
        } else {
            map1[a[i]] = s[i];
        }
        
        if (map2.find(s[i]) != map2.end()) {
            if (map2[s[i]] != a[i]) {
                return "NO";
            }
        } else {
            map2[s[i]] = a[i];
        }
    }
    
    return "YES";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int tt;
    cin >> tt;
    while (tt--) {
        int n, m;
        cin >> n;
        int arr[n];
        for (int i = 0; i < n; i++) {
            cin >> arr[i];
        }
        cin >> m;
        while (m--) {
            string s;
            cin >> s;
            string r = check(arr, n, s);
            cout << r << endl;
        }
    }
    return 0;
}

代码(Python):

def check(a, s):
    if len(a) != len(s):
        return "NO"
    
    map1 = {}
    map2 = {}
    for i in range(len(a)):
        if a[i] in map1:
            if map1[a[i]] != s[i]:
                return "NO"
        else:
            map1[a[i]] = s[i]
        if s[i] in map2:
            if map2[s[i]] != a[i]:
                return "NO"
        else:
            map2[s[i]] = a[i]
    return "YES"
def main():
    t = II()
    res = []
    for _ in range(t):
        n = II()
        a = LII()
        m = II()
        for _ in range(m):
            s = input()
            r = check(s, a)
            res.append(r)
    
    for r in res:
        print(r)

题目D:

Problem - D - Codeforces

题目大意解析和解题思路:

给你一列数字a,然后给你一个只含有”L“和"R"的字符串S,字符串长度和数组长度相同

然后可以选择字符串的两个索引l和r进行操作,保证l < r 且S[l] = 'L', S[r] = 'R'

然后把数组从索引l 到 r(包含l, r)的所有数字相加

把字符串从索引 l 到 r 的所有字符变成 ‘.’表示这一段不能再选了

现在要求能得到数字和的最大值

比如

6

3 5 1 4 3 2

LRLLLR

输出:

18(选第一个L和最后一个R,那就是整个数组的和)

输入:

5

1 2 3 4 5

LRLRR

输出

22

先选择索引2和 3

sum = 3 + 4 = 7

字符串变成 LR..R

再选择索引0和4

sum = 7 + 1 + 2 + 3 + 4 + 5 = 22

有时候吧,不能被题目条件和要求给误导,换一个思路就好解决了,反着思考

题目的要求只能从”内部“往”外部“相加,保证”l 到 r 的所有字符变成 ‘.’表示这一段不能再选了

满足这个条件

但是往另一个角度想,先加外层的,选择最左边的L和最右边的R,先相加,然后再加内部的L和R

结果是一样的

比如示例2,先选择索引0和4,sum = 1 + 2 + 3 + 4 + 5 = 15

然后再向内部移动,选择当前最左边的L和最右边的R

现在就可以选择索引2 和 3,sum = 15 + 3 + 4 = 22

那么可以维护两个指针,一个在左,一个在右,选择L和R,然后对这一段区间进行相加

代码示例(Python):

我写了注释,应该能看懂

def solve(n, a, s):
    res = 0
    #分别记录两个位置
    L_idx = -1
    R_idx = n
    #先找第一个位置
    for i in range(n):
        if s[i] == 'L':
            L_idx = i
            break
    for i in range(n - 1, -1, -1):
        if s[i] == 'R':
            R_idx = i
            break
    #如果没有L或者没有R,或者第一个L的位置大于R,那么最终和是0,直接返回res
    if L_idx == -1 or R_idx == n or L_idx > R_idx:
        return res
    
    while L_idx < R_idx:
        res += sum(a[L_idx : R_idx + 1])
        x1, x2 = L_idx, R_idx
        for i in range(L_idx + 1, n):
            if s[i] == 'L':
                L_idx = i
                break
        for i in range(R_idx - 1, -1, -1):
            if s[i] == 'R':
                R_idx = i
                break
        #查看下标是否变动
        if x1 == L_idx or x2 == R_idx:
            break
    return res

这个方法有个问题,就是每次都需要算一个区间的和,如果字符串是类似这样:LLLLRRRR,当数据量大的时候需要计算很多次,时间复杂度接近O(n^2),是会超时的

既然每次计算都要计算某个区间,那么可以使用前缀和

构建一个前缀和数组:

    prefix_sum = [0] * (n + 1)
    for i in range(n):
        prefix_sum[i + 1] = prefix_sum[i] + a[i]

那么从 L_idx 到 R_idx 的和为:

        res += prefix_sum[R_idx + 1] - prefix_sum[L_idx]

(之后更新一篇详细的前缀和文章,堆的文章好像有点多了.)

完整代码:

写了比较详细的注释,应该能看懂的

代码(C++):

C++需要注意数据范围,答案和前缀和数组都是long long类型

long long solve(int n, vector<int>& a, string& s) {
    // 构建前缀和数组
    vector<long long> prefix_sum(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        prefix_sum[i + 1] = prefix_sum[i] + a[i];
    }

    long long res = 0;
    int L_idx = -1, R_idx = n;

    // 找到第一个 'L' 的位置
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'L') {
            L_idx = i;
            break;
        }
    }

    // 找到最后一个 'R' 的位置
    for (int i = n - 1; i >= 0; --i) {
        if (s[i] == 'R') {
            R_idx = i;
            break;
        }
    }

    // 如果没有 'L' 或者 'R',或者第一个 'L' 的位置大于最后一个 'R'
    if (L_idx == -1 || R_idx == n || L_idx > R_idx) {
        return res;
    }

    while (L_idx < R_idx) {
        // 使用前缀和计算从 L_idx 到 R_idx 的和
        res += prefix_sum[R_idx + 1] - prefix_sum[L_idx];

        int x1 = L_idx, x2 = R_idx;

        // 找到下一个 'L' 的位置
        for (int i = L_idx + 1; i < n; ++i) {
            if (s[i] == 'L') {
                L_idx = i;
                break;
            }
        }

        // 找到下一个 'R' 的位置
        for (int i = R_idx - 1; i >= 0; --i) {
            if (s[i] == 'R') {
                R_idx = i;
                break;
            }
        }

        // 如果下标没有变化,退出循环
        if (x1 == L_idx || x2 == R_idx) {
            break;
        }
    }

    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
        }
        string s;
        cin >> s;

        cout << solve(n, a, s) << endl;
    }

    return 0;
}

代码(Python):

def solve(n, a, s):
    # 构建前缀和数组
    prefix_sum = [0] * (n + 1)
    for i in range(n):
        prefix_sum[i + 1] = prefix_sum[i] + a[i]
    
    res = 0
    L_idx = -1
    R_idx = n
    
    # 找到第一个 'L' 的位置
    for i in range(n):
        if s[i] == 'L':
            L_idx = i
            break
    
    # 找到最后一个 'R' 的位置
    for i in range(n - 1, -1, -1):
        if s[i] == 'R':
            R_idx = i
            break
    
    # 如果没有 'L' 或者没有 'R',或者第一个 'L' 的位置大于最后一个 'R'
    if L_idx == -1 or R_idx == n or L_idx > R_idx:
        return res
    
    while L_idx < R_idx:
        # 使用前缀和计算从 L_idx 到 R_idx 的和
        res += prefix_sum[R_idx + 1] - prefix_sum[L_idx]
        
        x1, x2 = L_idx, R_idx
        
        # 找到下一个 'L' 的位置
        for i in range(L_idx + 1, n):
            if s[i] == 'L':
                L_idx = i
                break
        
        # 找到下一个 'R' 的位置
        for i in range(R_idx - 1, -1, -1):
            if s[i] == 'R':
                R_idx = i
                break
        
        # 如果下标没有变化,退出循环
        if x1 == L_idx or x2 == R_idx:
            break
    
    return res

def main():
    t = II()
    res = []
    for _ in range(t):
        n = II()
        a = LII()
        s = input()
        res.append(solve(n, a, s))
    
    for r in res:
        print(r)

        我之后也会陆续更新一些比较高质量的基础算法详细解释,和一些解算法题的小技巧

        如果感兴趣,可以看看我之前写的文章:

        定长滑动窗口算法详细解释(带例题的详细解法)-CSDN博客

        不定长滑动窗口算法详细解释(带例题的详细解法)-CSDN博客

        算法题练习小技巧之区间合并--套路详细讲解带例题和源码(Python,C++)-CSDN博客

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值