比赛链接:https://codeforces.com/contest/1989
A. Catch the Coin
题意
给
n
n
n 个硬币坐标
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi),起始位置为
(
0
,
0
)
(0,0)
(0,0),每次行动可以向上下左右和斜向的八个方向移动一步,行动一次后,硬币会下移一格,即从
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 移动到
(
x
i
,
y
i
−
1
)
(x_i,y_i-1)
(xi,yi−1)。
对于每个硬币,确认是否能走到该硬币中的位置从而捡起硬币。
数据范围
n n n ( 1 ≤ n ≤ 500 1 \le n \le 500 1≤n≤500), x i x_i xi and y i y_i yi ( − 50 ≤ x i , y i ≤ 50 -50 \le x_i, y_i \le 50 −50≤xi,yi≤50)
思路
显然最优的策略是走到硬币的正下方位置,因为硬币会一直下移,所以如果能走到硬币的正下方位置一定能走到硬币的位置。
复杂度
时间复杂度为 O ( ∣ x i ∣ ) O(|x_i|) O(∣xi∣)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int x, y;
cin >> x >> y;
int dx = 0;
// dx为x轴偏移量
if (x > 0)
dx = 1;
else if (x < 0)
dx = -1;
int xi = 0, yi = 0, f = 0;
// (xi,yi) 为当前走到位置,起始为 (0,0)
do {
if (f)
y--;
f = 1;
// 要注意的是,是移动一步之后,硬币才下移一格,所以要用变量f标记一下此时是否走了一步
if (xi != x)
xi += dx;
yi--;
} while (xi != x);
// 判断是否是在正下方(包含相等的情况)
if (yi <= y)
cout << "YES\n";
else
cout << "NO\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
solve();
}
}
B. Substring and Subsequence
题意
给两个字符串 a a a 和 b b b,计算包含 a a a 为子串 和 把 b b b 作为子序列的字符串的最小长度。
数据范围
a a a ( 1 ≤ ∣ a ∣ ≤ 100 1 \le |a| \le 100 1≤∣a∣≤100), b b b ( 1 ≤ ∣ b ∣ ≤ 100 1 \le |b| \le 100 1≤∣b∣≤100),
思路
令构造的字符串为
s
s
s。
因为
a
a
a 为
s
s
s 的子串,所以
b
b
b 要么会有部分存在在
a
a
a 中, 要么就没有。
1,如果
b
b
b 没有部分存在
a
a
a 中,那么最优的方法就是令
s
=
a
+
b
s = a+b
s=a+b,此时
s
s
s 长度就是
∣
a
∣
+
∣
b
∣
|a| + |b|
∣a∣+∣b∣。
2,如果
b
b
b 有部分存在
a
a
a 中,若这部分长度为
x
x
x,那么
s
s
s 的最小长度为
∣
a
∣
+
∣
b
∣
−
x
|a| + |b| - x
∣a∣+∣b∣−x。
因为字符串长度不大,所以可以枚举
b
b
b 中每一位字母作为
∣
a
∣
|a|
∣a∣ 中存在部分的开头,然后进行匹配,求出这一位作为开头时能在
∣
a
∣
|a|
∣a∣ 中部分的最长长度
x
x
x。
上述两种情况的较小值即为答案。
复杂度
时间复杂度为 O ( ∣ a ∣ ∗ ∣ b ∣ ) O(|a|*|b|) O(∣a∣∗∣b∣)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
string sa, sb;
cin >> sa >> sb;
int n1 = sa.size(), n2 = sb.size(), ans = n1 + n2;
for (int i = 0; i < n2; i++) {
int j = i;
// j为从i开始匹配的最大下标,初始为i
for (int k = 0; k < n1; k++) {
if (j < n2 && sa[k] == sb[j])
j++;
// 每找到字符串sa中有一位和当前sb[j]相同,j后移一位
}
int cur = n1 + n2 - (j - i);
// j-i即为匹配的最大部分长度
ans = min(ans, cur);
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
solve();
}
}
C. Two Movies
题意
有两部电影,有
n
n
n 个人,第
i
i
i 个人对第一个电影的评价值为
a
i
a_i
ai,对第二个电影的评价值为
b
i
b_i
bi。
每个人可以选择其中一部电影留下评论,电影的评分为留下评论的评价值总和,公司的评分是两部电影评分的最小值,求公司可能获得的最大评分。
数据范围
n n n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10^5 1≤n≤2⋅105), − 1 ≤ a i , b i ≤ 1 -1 \le a_i,b_i \le 1 −1≤ai,bi≤1
思路
对于第
i
i
i 个人的评价
(
a
i
,
b
i
)
(a_i,b_i)
(ai,bi) 进行分类讨论:
1,如果
a
i
=
0
,
b
i
=
0
a_i = 0,b_i =0
ai=0,bi=0,不会造成任何贡献,可忽略。
2,如果
a
i
=
0
/
−
1
,
b
i
=
1
a_i = 0/-1,b_i = 1
ai=0/−1,bi=1,显然让第
i
i
i 个人评论第二步电影是最优的,因为评价第一部电影只会让最小值不变或者更小,评价第二部电影则会让最小值不变或者增大。(把
a
i
,
b
i
a_i,b_i
ai,bi 的数值反过来也是同理)。
因为这种情况是可以直接确定要评价哪一个的,所以可以先处理这种情况,然后得到两部电影的得分
s
a
,
s
b
sa,sb
sa,sb。
3,如果
a
i
=
1
,
b
i
=
1
a_i=1,b_i =1
ai=1,bi=1,对于这种情况,选择加在
s
a
,
s
b
sa,sb
sa,sb 中的较小值是最优的,因为这会使得最小值增大。
同样的,也是把这种情况全部处理后再处理下一种情况。
4,如果
a
i
=
−
1
,
b
i
=
−
1
a_i=-1,b_i=-1
ai=−1,bi=−1,对于这种情况,选择减再
s
a
,
s
b
sa,sb
sa,sb 中的较大值是最优的。
处理完上述所有情况之后,
m
i
n
(
s
a
,
s
b
)
min(sa,sb)
min(sa,sb) 即为答案。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n;
int a[N], b[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int c1 = 0, c2 = 0, sa = 0, sb = 0;
// c1记录(-1,-1)的数量
// c2记录(1,1)的数量
for (int i = 1; i <= n; i++) {
cin >> b[i];
if (a[i] == -1 && b[i] == -1) {
c1++;
} else if (a[i] == 1 && b[i] == 1) {
c2++;
} else {
// 经过前面两个判断,如果 a[i]=b[i],只有(0,0)的情况,不影响
// 否则就是(1,0/-1) 或者 (0/-1,1)的情况,往1对应的分数加即可
a[i] >= b[i] ? sa += a[i] : sb += b[i];
}
}
while (c2--)
sa <= sb ? sa++ : sb++;
while (c1--)
sa >= sb ? sa-- : sb--;
cout << min(sa, sb) << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
solve();
}
}
D. Smithing Skill
题意
有
n
n
n 个技能,
m
m
m 种金属。
1,第
i
i
i 种金属有
c
i
c_i
ci 个。
2,第
i
i
i 个技能可以花费
a
i
a_i
ai 个任意种金属制造第
i
i
i 种武器(制造武器只能用一种金属,不过可以任意选择种类),第
i
i
i 种武器熔化后可以返还
b
i
b_i
bi 个制造时用的对应种类金属。
制造和熔化都能获得一点经验值,问能得到的最大经验值是多少?
数据范围
1 ≤ n , m ≤ 1 0 6 1 \le n, m \le 10^6 1≤n,m≤106, 1 ≤ a i ≤ 1 0 6 1 \le a_i \le 10^6 1≤ai≤106, 0 ≤ b i < a i 0 \le b_i < a_i 0≤bi<ai, 1 ≤ c j ≤ 1 0 9 1 \le c_j \le 10^9 1≤cj≤109
思路
因为制造武器只能用一种金属,所以对于每种金属,只需要考虑数量和经验值的关系。
令
f
(
i
)
f(i)
f(i) 为金属数量为
i
i
i 时能获得的最大经验值。
因为数量为
i
−
1
i-1
i−1 能取到经验值
x
x
x,数量加一也不影响能取得的经验值,所以
f
(
i
)
f(i)
f(i) 可以从
f
(
i
−
1
)
f(i-1)
f(i−1) 转移过来。
对于每件武器可以制造完后就熔化,得到
2
2
2 点经验值,消耗的金属数为
a
i
−
b
i
a_i - b_i
ai−bi,且要求金属制造前的数量不小于
a
i
a_i
ai,因此
f
(
i
)
f(i)
f(i) 可以从
f
(
i
−
(
a
j
−
b
j
)
)
f(i-(a_j-b_j))
f(i−(aj−bj)) 转移过来,且经验值加
2
2
2 。
注意到,
f
(
i
)
f(i)
f(i) 是单调不减的,即
i
i
i 越大,
f
(
i
)
f(i)
f(i) 越大,所以
a
j
−
b
j
a_j-b_j
aj−bj 可以取最小值,从
f
(
i
−
m
i
n
(
a
j
−
b
j
)
)
f(i-min(a_j-b_j))
f(i−min(aj−bj)) 转移过来。
因此,
f
(
i
)
=
m
a
x
(
f
(
i
−
1
)
,
f
(
i
−
m
i
n
(
a
j
−
b
j
)
)
+
2
)
)
f(i) = max(f(i-1),f(i-min(a_j-b_j))+2))
f(i)=max(f(i−1),f(i−min(aj−bj))+2))
但是,观察数据范围,发现
i
i
i 的上界是
1
0
9
10^9
109,貌似是没办法递推,但是
a
i
a_i
ai 的上界是
1
0
6
10^6
106,意味着当
i
>
1
0
6
i>10^6
i>106 时,可以取得
n
n
n 种武器中消耗最小的进行消耗,即选择最小的
a
i
−
b
i
a_i-b_i
ai−bi 进行减少,每次减少增加
2
2
2 点经验值,直到减少到不大于
1
0
6
10^6
106,然后直接通过
f
(
i
)
f(i)
f(i) 计算得到最大值。
因此,令
i
1
i_1
i1 为
i
i
i 减到不大于
1
0
6
10^6
106 时的数值,则
f
(
i
)
=
⌈
i
−
1
0
6
m
i
n
(
a
i
−
b
i
)
⌉
∗
2
+
f
(
i
1
)
f(i) = \lceil \frac{i-10^6}{min(a_i-b_i)}\rceil*2+f(i_1)
f(i)=⌈min(ai−bi)i−106⌉∗2+f(i1)
此时需要考虑的问题就是,要怎么快速求出当金属数量为
x
x
x 时,能取到的最小的
a
i
−
b
i
a_i-b_i
ai−bi ?显然,直接遍历去计算复杂度是
O
(
n
∗
m
)
O(n*m)
O(n∗m) 会超时。
令
d
(
i
)
d(i)
d(i) 为金属数量为
i
i
i 是能取到的最小
a
i
−
b
i
a_i-b_i
ai−bi。
注意到,金属数量
i
i
i 和取到的最小
a
i
−
b
i
a_i-b_i
ai−bi 成单调不增的关系,所以和求
f
(
i
)
f(i)
f(i) 类似,
d
(
i
)
d(i)
d(i) 可以从
d
(
i
−
1
)
d(i-1)
d(i−1) 转移而来,且对于第
i
i
i 种武器,可以直接使
d
(
a
i
)
d(a_i)
d(ai) 与
a
i
−
b
i
a_i -b_i
ai−bi 比较取最小值。
综上,
d
(
i
)
=
m
i
n
(
d
(
i
−
1
)
,
a
i
−
b
i
)
(
a
i
=
i
且
1
≤
i
≤
n
)
d(i) = min(d(i-1),a_i-b_i)(a_i=i 且 1\le i \le n)
d(i)=min(d(i−1),ai−bi)(ai=i且1≤i≤n),则
f
(
i
)
=
m
a
x
(
f
(
i
−
1
)
,
f
(
i
−
d
(
i
)
)
+
2
)
f(i) = max(f(i-1),f(i-d(i))+2)
f(i)=max(f(i−1),f(i−d(i))+2)。
最后的答案为所有金属能获得的最大经验值的总和,即 ∑ i = 1 m f ( c i ) \sum^m_{i=1}f(c_i) ∑i=1mf(ci)。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
int n, m, k;
int a[N], b[N], c[N], d[N], f[N];
void solve()
{
cin >> n >> m;
memset(d, 0x3f, sizeof(d));
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
d[a[i]] = min(d[a[i]], a[i] - b[i]);
}
for (int i = 1; i < N; i++) {
d[i] = min(d[i], d[i - 1]);
}
for (int i = 1; i < N; i++) {
f[i] = f[i - 1];
if (i - d[i] >= 0) {
f[i] = max(f[i], f[i - d[i]] + 2);
}
}
int ans = 0, del = d[N - 1];
// d[i] 为金属数量为i时取得的最小消耗 a[j]-b[j]
// d[N-1] 即为 n种武器能取得的最小消耗
for (int i = 1; i <= m; i++) {
cin >> c[i];
// 不一定是减到1e6以内,只要减到定义的上界N以内即可,N>=1e6
if (c[i] >= N) {
int k = c[i] - (N - 1);
int v = (k + del - 1) / del;
// v为减到N以内的操作次数
ans += 2 * v;
c[i] -= del * v;
}
ans += f[c[i]];
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}