ABC353 A-E题解
下面的内容不包括题目翻译,要想获取题目翻译,请参照 这篇教程 来获取题目翻译。
A
题目
从左到右遍历数组,找到第一个比数组第一个元素大的元素,输出坐标即可。如果没有输出 -1
即可。
AC Code(CPP)
#include <bits/stdc++.h>
using namespace std;
int n, h[200100];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> h[i];
if (h[i] > h[1]) {
cout << i << '\n';
return 0;
}
}
cout << -1;
return 0;
}
AC Code(Python)
n = int(input())
a = [int(i) for i in input().split()]
flag = 0
for i in range(len(a)):
if a[i] > a[0]:
print(i + 1)
flag = 1
break
print(("-1", "")[flag])
B
题目
就是个模拟,按照题目所给信息编码即可。
AC Code(CPP)
#include <bits/stdc++.h>
using namespace std;
int n, k, a[200100];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int cnt = 1, tmp = 0;
for (int i = 1; i <= n; i++) {
if (a[i] <= k - tmp) {
tmp += a[i];
}
else {
tmp = a[i];
cnt++;
}
}
cout << cnt;
return 0;
}
AC Code(Python)
n, k = [int(i)for i in input().split()]
a = [int(i)for i in input().split()]
cnt = 0
Sum = 0
for i in a:
if Sum + i > k:
Sum = 0
cnt += 1
Sum += i
print(cnt + 1)
C
题目
可以发现,无论数组如何排序,每一个 A i A_i Ai 都在一个 形如 ( A x , A y ) (A_x,A_y) (Ax,Ay) 的对中,所以数组的顺序并不重要。
因为所有 A i ≤ 1 0 8 A_i\le 10^8 Ai≤108,所以 A i + A j A_i + A_j Ai+Aj 除以 1 0 8 10^8 108 的余数要么是 A i + A j − 1 0 8 ( A i + A j > 1 0 8 ) A_i+A_j - 10^8(A_i+A_j>10^8) Ai+Aj−108(Ai+Aj>108),要么是 A i + A j ( A i + A j < 1 0 8 ) A_i + A_j(A_i+A_j < 10^8) Ai+Aj(Ai+Aj<108)
可以将数组排序,发现对于每一个 A i A_i Ai,如果 A i + A j > 1 0 8 A_i+A_j>10^8 Ai+Aj>108,那么 A i + A j + 1 A_i+A_{j+1} Ai+Aj+1 定然大于 1 0 8 10^8 108。发现满足单调性,可以二分,然后用前缀和求出后面所有的和,超过 1 0 8 10^8 108 的段减去相应个数的 1 0 8 10^8 108,在知道分界线坐标后,上述所有操作都可以在 O ( 1 ) O(1) O(1) 的时间复杂度内求出,加上二分和排序的时间复杂度就是 O ( N log 2 N ) O(N\log_2N) O(Nlog2N),十分优秀。
此题使用 Python
实现的代码耗时 1293ms,隔壁 C++
只用了 43ms,30倍的时间差距不容小觑。在用 Python
实现代码是一定要注意常数问题,因为这个语言本来就慢。
AC Code(CPP)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, a[300100];
int sum[300100], ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
a[n + 1] = 100000001;
for (int i = 1; i < n; i++) {
int bas = 100000000 - a[i];
int l = lower_bound(a + i + 1, a + n + 1, bas) - a - 1;
ans += sum[n] - sum[i] - (n - l) * 100000000 + a[i] * (n - i);
}
cout << ans << '\n';
return 0;
}
AC Code(Python)
n = int(input())
a = [int(i) for i in input().split()]
a.sort()
Sum = [a[0]]
for i in range(1, n):
Sum.append(a[i] + Sum[i - 1])
# 由于 python 中从 0 开始的下标,所以实现起来会相较于 c++ 来说会有点不同。
ans = 0
for i in range(n - 1):
bas = 100000000 - a[i]
# 这里相较于 c++ 来说是手写的二分,而不是用 lower_bound 。
l, r = i + 1, n
while l < r:
mid = (l + r) // 2
if a[mid] >= bas:
r = mid
else:
l = mid + 1
ans += Sum[n - 1] - Sum[i] + a[i] * (n - 1 - i)
if l < n:
ans -= (n - l) * 100000000
print(ans)
D
题目
此题将 f ( x , y ) f(x, y) f(x,y) 转换为纯数学运算可以更好地寻找规律。
我们发现, f ( 11451 , 41919810 ) f(11451, 41919810) f(11451,41919810) 等于这个:
1145141919810 1145141919810 1145141919810
等于
11451 × 100000000 + 41919810 11451 \times 100000000 +41919810 11451×100000000+41919810
所以将其转换成数学运算表达方式等于这个:
f ( x , y ) = x ⋅ 1 0 ⌊ l o g 10 y ⌋ + y f(x,y)=x \cdot 10^{\lfloor log_{10}^y \rfloor} + y f(x,y)=x⋅10⌊log10y⌋+y
仿佛一个一个计算特别麻烦,但是我们可以将数组分成 10 10 10 个集合,每一个集合为 S i S_i Si,对应 A A A 中 i i i 位数的下标。那么可以如此计算:
∑ i = 1 N ∑ j = 1 10 ∑ k ∈ S j f ( A i , A k ) [ k > i ] \sum_{i=1}^N\sum_{j=1}^{10}\sum_{k\in S_j} f(A_i, A_k)[k>i] i=1∑Nj=1∑10k∈Sj∑f(Ai,Ak)[k>i]
此时,第三个循环中 A k A_k Ak 可以用前缀和预处理出来,又由于 A i A_i Ai 的系数固定,可以 O ( 1 ) O(1) O(1) 处理出来。
此时时间复杂度就变成了 O ( 10 n ) O(10n) O(10n),十分优秀。
注意 C++
中取模问题,代码中我使用了后缀和,更方便,但是前缀和与后缀和没什么区别。请再次注意 python
的运行常数,本题我给的 python
代码运行时间 1600ms,c++
代码运行时间 40ms,是 python
的
1
40
\frac1{40}
401
AC Code(CPP)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, a[200100];
int sum[15][200100], cnt[15][200100];
int p10[20], ans;
int f(int x) {
return floor(log10(x)) + 1;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= 11; j++) {
sum[j][i] = sum[j][i + 1];
cnt[j][i] = cnt[j][i + 1];
}
sum[f(a[i])][i] += a[i], cnt[f(a[i])][i] += 1;
sum[f(a[i])][i] %= 998244353ll;
}
p10[0] = 1;
for (int i = 1; i <= 11; i++) {
p10[i] = p10[i - 1] * 10;
p10[i] %= 998244353ll;
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= 11; j++) {
ans += (sum[j][i + 1] + (a[i] % 998244353ll) * cnt[j][i + 1] %
998244353ll * p10[j] % 998244353ll) % 998244353ll;
ans %= 998244353ll;
}
}
cout << ans % 998244353ll;
return 0;
}
AC Code(Python)
from math import *
n = int(input())
a = [int(i) for i in input().split()]
def f(x):
return floor(log10(x)) + 1
p10 = [1]
for i in range(12):
p10.append(p10[i] * 10)
Sum, cnt = [], []
for i in range(n + 2):
Sum.append([0] * 12)
cnt.append([0] * 12)
for i in range(n - 1, -1, -1):
for j in range(0, 12):
Sum[i][j] = Sum[i + 1][j]
cnt[i][j] = cnt[i + 1][j]
Sum[i][f(a[i])] += a[i]
cnt[i][f(a[i])] += 1
ans = 0
for i in range(n - 1):
for j in range(1, 12):
ans += p10[j] * a[i] * cnt[i + 1][j] + Sum[i + 1][j]
ans %= 998244353
print(ans)
E
题目
考虑暴力:
先枚举 i , j i,j i,j,再暴力寻找最长前缀,其时间复杂度可以被卡到 O ( N 2 ) O(N^2) O(N2) 以上。
考虑优化:
我们发现,对于每一个字符串,暴力查找时前面共同前缀部分被重复计算了很多次,我们尅确定每一个 i i i,再遍历字符串,再遍历数组,一旦发现有一个串已经枚举过了公共前缀的最后一个字符,加上答案,不管这个字符串了,这样时间复杂度可以被优化一部分,但是最大值仍然是 O ( N ∑ i = 1 n ∣ S i ∣ ) O(N\sum_{i=1}^n|S_i|) O(N∑i=1n∣Si∣) 的,不可取。
我们发现,由于公共前缀都在当前字符串上,所以寻找公共前缀部分可以用字典树(也称 trie 树)实现,对于每一个节点,统计当前位置是这个节点表示的字符的个数(表示为 cnt
),我们遍历字符串,遍历的答案加上该节点的 cnt
,最后返回遍历的答案即可。
由于走到某节点的所有字符串该节点前的前缀都一样,所以字典树是永远正确的。
AC Code(CPP)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
string s[300100];
bool operator <(string a, string b) {
return a.size() < b.size();
}
struct node{
int p, nxt[30], cnt;
};
node t[4000100];
int cnt = 0;
void add(string s) {
int p = 0;
for (int i = 0; i < (int)s.size(); i++) {
if (!t[p].nxt[s[i] - 'a']) {
t[p].nxt[s[i] - 'a'] = ++cnt;
}
p = t[p].nxt[s[i] - 'a'];
t[p].cnt++;
}
}
int calc(string s) {
int p = 0;
int ret = 0;
for (int i = 0; i < (int)s.size(); i++) {
ret += t[p].cnt;
p = t[p].nxt[s[i] - 'a'];
}
ret += t[p].cnt;
return ret;
}
int ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
add(s[i]);
}
for (int i = 1; i <= n; i++) {
ans += calc(s[i]) - (int)s[i].size();
}
cout << ans / 2;
return 0;
}
AC Code(Python)
n = int(input())
a = [str(i) for i in input().split()]
cnt, Next = [], []
def add_node():
cnt.append(0)
temp = []
for i in range(30):
temp.append(0)
Next.append(temp)
def insert(s):
p = 0
for i in s:
now_char = ord(i) - 97
if Next[p][now_char] == 0:
add_node()
Next[p][now_char] = len(cnt) - 1
p = Next[p][now_char]
cnt[p] += 1
def calc(s):
p, ret = 0, 0
for i in range(len(s)):
now_char = ord(s[i]) - 97
p = Next[p][now_char]
ret += cnt[p]
return ret - len(s)
add_node()
for i in range(n):
insert(a[i])
ans = 0
for i in range(n):
ans += calc(a[i])
print(ans // 2)