前言
考前了,回归两三天
A. 两数归零
a. 暴力策略。即枚举 ( i , j ) (i,j) (i,j),判断 a i + a j = 0 a_{i} + a{j} = 0 ai+aj=0。复杂度 O ( N 2 ) O(N^2) O(N2),可得 30 ∼ 60 30 \sim 60 30∼60 分;
b. 满分策略。注意到两数之和为 0 0 0,则这两个数必定为相反数。所以开两个数组,一个统计每一个正数的数量,一个统计每一个负数的数量,绝对值相等的两个数的数量之积,再加和即可。值得注意的是,还应该注意 0 0 0,因为 0 + 0 = 0 0+0=0 0+0=0,所以还需要统计 0 0 0 的数量 x x x,最后结果加上 C x 2 C_x^2 Cx2 即可。复杂度 O ( N ) O(N) O(N),但我的代码里用到了一次排序,所以 O ( N log N ) O(N \log N) O(NlogN),可得 100 100 100 分。
代码稍微有点臃肿了,凑活着看看吧。
#include <iostream>
#include <algorithm>
#include <unordered_map>
const int N = 3e5 + 9;
int n, a[N], b[N];
int ca, cb, cnt_zero;
std::unordered_map<int, int> mp;
int main() {
scanf("%d", &n);
for (int i = 1, x; i <= n; i++) {
scanf("%d", &x);
if (x == 0) cnt_zero++;
if (mp[x] == 0) {
if (x < 0) a[++ca] = -x;
else if (x > 0) b[++cb] = x;
}
mp[x]++;
}
std::sort(a + 1, a + ca + 1);
std::sort(b + 1, b + cb + 1);
int ans = 0;
for (int i = 1; i <= ca; i++) {
int pos = std::lower_bound(b + 1, b + cb + 1, a[i]) - b;
if (b[pos] != a[i]) continue;
ans += mp[-a[i]] * mp[b[pos]];
}
ans += cnt_zero * (cnt_zero - 1) / 2;
printf("%d\n", ans);
return 0;
}
B. 牛奶供应(四)
这道题只需要提笔算一算就能做。
其实要求的就是:
∑
i
=
1
n
(
(
m
−
d
i
)
×
b
i
[存储费]
+
p
i
×
b
i
[材料费]
)
\sum _{i=1} ^{n} ((m-d_{i}) \times b_i \text{[存储费]} + p_i \times b_i \text{[材料费]})
i=1∑n((m−di)×bi[存储费]+pi×bi[材料费])
即:
∑
i
=
1
n
(
(
m
−
d
i
+
p
i
)
×
b
i
)
\sum _{i=1} ^{n} ((m-d_{i}+p_{i}) \times b_i)
i=1∑n((m−di+pi)×bi)
其中,
b
i
b_i
bi 为第
i
i
i 天购买的材料升数。
注意到式子中 m − d i + p i m-d_{i}+p{i} m−di+pi 是定值,所以只需要按照该定值对 i i i 天的花费排序,最小值那天买最多的原料,以此类推即可。
#include <iostream>
#include <algorithm>
using LL = long long;
const int N = 1e5 + 9;
int n, m, l;
int w[N];
struct Node {
int num, k;
} day[N];
int main() {
scanf("%d%d%d", &n, &m, &l);
for (int i = 1, d, p; i <= n; i++) {
scanf("%d%d%d", &d, w + i, &p);
day[i] = {i, m - d + p};
}
std::sort(day + 1, day + n + 1, [](const Node x, const Node y) {
if (x.k == y.k) return x.num > y.num;
return x.k < y.k;
});
int i = 1;
LL ans = 0;
while (l) {
if (w[day[i].num] >= l) {
ans += l * day[i].k;
l = 0;
} else {
ans += w[day[i].num] * day[i].k;
l -= w[day[i].num];
}
i++;
}
printf("%lld\n", ans);
return 0;
}
C. 工作安排
贪心。策略是看每两天前后怎么安排才能更省钱。
#include <iostream>
#include <algorithm>
const int N = 2e5 + 9;
struct Node {
int t, f;
} task[N];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d", &task[i].t, &task[i].f);
std::sort(task + 1, task + n + 1, [](const Node x, const Node y) {
return x.f*(x.t+y.t)+y.f*y.t > y.f*(x.t+y.t)+x.f*x.t;
});
long long ans = 0;
int cur = 0;
for (int i = 1; i <= n; i++) {
cur += task[i].t;
ans += task[i].f * cur;
}
printf("%lld", ans);
return 0;
}
D. 单词解密
这题我个人认为我的解法比较的妙,当然其实也是很基本的解法。
一个形如 abc
的字符串,对于字符 b
显然要考虑这几种情况:
a. b
单独一个,构成
b
‾
\overline{b}
b;
b. b
跟着前面的字符 a
,构成
a
b
‾
\overline{ab}
ab;
c. b
跟着后面的字符 c
,构成
b
c
‾
\overline{bc}
bc
但并不是对于所有的字符都有这样的情况。当 a
所表示的数字太大时,情况
b
b
b 不成立;当 b
太大时,情况
c
c
c 又不成立。即只要
a
b
‾
\overline{ab}
ab 或
b
c
‾
\overline{bc}
bc 都大于
26
26
26 时,只能取情况
a
a
a。
这就是这道题讨论的重点,即什么时候能够选取相邻两个数字构成字母。
为了更好的分析,我们引入一个样例:124666899321
将所有相邻两数字能够构成字母的划出来:
没有横线的部分显然都只有 1 1 1 种划分方法。所以我们只重点讨论有横线的部分。
对于
12
12
12,可以拆分开来看,即
1
1
1 和
2
2
2,对应字母 a
和 b
。
后面还接了一个
4
4
4,
4
4
4 显然可以跟前面的
2
2
2 构成
24
24
24(x
),也可以单独作为
4
4
4(d
)。如果从递推的角度看的话,其实本质是在
12
12
12 后面接了一个数字,因为
12
12
12 对应了两种情况,而在
12
12
12 后面加一个数字,
124
124
124 所对应的情况数即为
12
12
12 的情况数加上一个特有的
24
24
24。
我们将其抽象化,圈代表单独的,横线代表相邻两个组成的数。
发现了什么?是不是有点似曾相识的感觉?
如果将末尾为圈的看作 α \alpha α 情形,末尾为横线的看作 β \beta β 情形,则 α \alpha α 情形的下一步必定可以有两种,而 β \beta β 情形必定有一种。
发现了什么?是不是有点似曾相识的感觉?
如果将 α \alpha α 情形看作两个月后的兔子,而 β \beta β 情形看作一个月大的兔子,这不就是兔子数列——斐波那契数列吗?
所以,一个长度为
n
n
n 的,满足
a
,
b
,
c
a,b,c
a,b,c 三种情况的字符串,其可能的情况数就是
F
(
n
)
F(n)
F(n),其中
F
(
0
)
=
1
,
F
(
1
)
=
1
,
F
(
i
)
=
F
(
i
−
1
)
+
F
(
i
−
2
)
(
i
≥
2
,
i
∈
N
∗
)
F(0)=1,F(1)=1,F(i)=F(i-1)+F(i-2)(i \geq 2, i \in \N^{*})
F(0)=1,F(1)=1,F(i)=F(i−1)+F(i−2)(i≥2,i∈N∗)
所以,这题的思路就是:将字符串划分为尽量少的若干部分,使得每个部分都满足任意相邻两个字符都能构成一个小于等于
26
26
26 的数,然后计算这些部分长度
l
k
l_{k}
lk,结果就是
Π
k
=
1
m
F
(
l
k
)
\Pi _{k=1} ^{m} F(l_{k})
Πk=1mF(lk)。其中,
m
m
m 代表划分段数。
吗?
上面的分析中,我们忽略了一个非常重要的数字——
0
0
0。如果这个字符串中含有 10
或 20
,那这个
0
0
0 是需要单独讨论的。因为
0
0
0 不能作为单独的一个(显然不存在第
0
0
0 个字母吧),也不能跟后面的数字合并(显然你不会说 a
是第
01
01
01 个字母吧)。
0
0
0 只有可能和前面的数字合并,成为 10
或者 20
。所以,如果碰到
0
0
0,我们还应该单独考虑,将它与前面的字符看作一个整体,而不能够简单地将其放在一个如前面所述的整体中计算。
#include <iostream>
using LL = long long;
const int N = 1e5 + 9;
const LL MOD = 1e9 + 7;
LL fib[N] = {1, 1, 2};
std::string s;
int main() {
for (int i = 3; i < N; i++)
fib[i] = (fib[i-1] + fib[i-2]) % MOD;
std::cin >> s;
LL ans = 1;
int cnt = 1;
for (int i = 1; i < s.size(); i++) {
int cur = (s[i-1] - '0') * 10 + s[i] - '0';
if (s[i] == '0') {
ans = (ans * fib[cnt-1]) % MOD;
cnt = 1, i++;
} else if (cur > 26) {
ans = (ans * fib[cnt]) % MOD, cnt = 1;
} else cnt++;
}
ans *= fib[cnt];
std::cout << ans % MOD << '\n';
return 0;
}
代码甚至还比前几题短点。