前言:
(这次比赛应该是codeforces这个网站的第2000场比赛吧,然后这次div3难度感觉像是div4?可能是我变强了(bushi),不过我还是一如既往的四题选手,不过这次四题速度挺快的,思路都会很快想出来的,很少想错,然后总排名是7000多一点,早知道注册账号后前面几把有额外加分就不这么随便了)
(system test 完,想吐槽一句,就是说都在最后几分钟提交是吧,90%到98%我名次没什么变化,然后最后2%名次掉了快1000了,本来能上绿的,现在差三分)
本文主要是题A到题D的详细思路和解法,如果觉得有帮助或者写的还不错可以点个赞
个人觉得D的题解写的比较详细
本次比赛题目链接:Dashboard - Codeforces Round 966 (Div. 3) - Codeforces
目录
题目A:
题目大意解析和解题思路:
题目大意就是一个人写下了一个数字,这个数字是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:
题目大意解析和解题思路:
题目可以理解依次上车,座位就是一列,然后有一个规则,就是上车的人必须前面或者后面有人
都有人也行(这种情况出现的前提是前面有人违规),然后给你一个数组,从前到后依次表示第一个人到最后一个人选择的位置,判断是否都按规则来了
用哈希表模拟就行了,遍历一遍数组,然后不断加入新元素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:
题目大意解析和解题思路:
这一题挺有意思的,描述也简单
就是说给你一个数组,然后若干个字符串,数组长度和字符串长度相同
数组里面两个相同数字的两个位置要保证字符串的这两个位置也相同
不同的两个数字对于的位置,字符串这两个位置的字符也不同
比如示例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:
题目大意解析和解题思路:
给你一列数字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)
我之后也会陆续更新一些比较高质量的基础算法详细解释,和一些解算法题的小技巧
如果感兴趣,可以看看我之前写的文章: