链接: https://codeforces.com/contest/1898
目录
A. Milica and String (暴力/模拟)
题意:
给定长度为 n n n 的 A B AB AB 字符串,问 字符串里有 k k k 个 B B B 至少需要多少次操作,该操作是选定一个下标 i i i 使得 s 1 − s i s_1-s_i s1−si 的字符变成 A A A 或 B B B,输出操作数、选定的下标和所变成的字符。
思路:
可以观察到数据很小优先考虑暴力模拟。容易发现,如果字符串中 B B B 的个数不足 k k k 个,只需要修改一次就能达到要求,该修改会把前 k − S c n t B k-Scnt_B k−ScntB 个 A A A覆盖掉。如果字符串中 B B B 的个数超过 k k k 个,只需要修改一次就能达到要求,该修改会把前 S c n t B − k Scnt_B-k ScntB−k 个 B B B 覆盖掉。
代码:
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
ll n, m, k, x, y, res = 0, sum = 0, ma = 0, mi = INF;
void solve()
{
ll n;
string s;
cin >> n >> k >> s;
ll ans = -1;
char ch = 'A';
map<char, int> mp;
for (int i = 0; i < n; i++) {
mp[s[i]]++;
}
int cnt = k - mp['B'];
if (!cnt) {
cout << 0 << endl;
return;
} else if (cnt < 0) {
res = 0; //B count
cnt = -cnt;
for (int i = 0; i < n; i++) {
if (s[i] == 'B')
res++;
if (res == cnt) {
ans = i + 1;
break;
}
}
} else {
res = 0; //A count
ch = 'B';
for (int i = 0; i < n; i++) {
if (s[i] == 'A')
res++;
if (res == cnt) {
ans = i + 1;
break;
}
}
}
cout << "1\n"
<< ans << ' ' << ch << endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
// cout.tie(0);
ll _;
cin >> _;
while (_--)
solve();
return 0;
}
B. Milena and Admirer(思维/贪心)
题意:
给定长度为 n n n 数字序列,问最少需要多少次分裂操作,可以使得序列非递减。其中分裂操作是将 a i a_i ai 分裂成 x x x 和 a i − x a_i-x ai−x ,然后放回原位置。
思路:
我们不考虑整个分裂的实时过程,只关心分裂了几次,和上一个分裂操作排在最前的数字是多少,很明显这个数字越大对前面数字影响就会越小。影响是后面影响前面所以遍历应当从后往前。先不考虑分裂成整数,可以想到,将 a i a_i ai 均分成 x x x 当 x < = a i + 1 x<=a_{i+1} x<=ai+1 时是最优的,此时 a i a_i ai 将更新为 c e i l ( x ) ceil(x) ceil(x) ,分裂后的数字数量为 c e i l ( a i / a i + 1 ) ceil(a_i/a_{i+1}) ceil(ai/ai+1) ,分裂操作数即为 c e i l ( a i / a i + 1 ) − 1 ceil(a_i/a_{i+1})-1 ceil(ai/ai+1)−1 。( c e i l ceil ceil表示向上取整)
代码:
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
ll n, m, k, x, y, res = 0, sum = 0, ma = 0, mi = INF;
void solve()
{
cin >> n;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[n - i + 1];
}
ans = 0;
for (int i = 2; i <= n; i++) {
if (a[i] > a[i - 1]) {
ll tmp = (a[i] + a[i - 1] - 1) / a[i - 1];
ans += tmp - 1;
a[i] /= tmp;
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
// cout.tie(0);
ll _;
cin >> _;
while (_--)
solve();
return 0;
}
C. Colorful Grid(思维/构造)
题意:
给定 n ∗ m n*m n∗m 的点,问从 ( 1 , 1 ) (1,1) (1,1) 到 ( n , m ) (n,m) (n,m) 存不存在线段数为 k k k 的一条连续且红蓝色线段交替的线路,如果有输出 Y E S YES YES 并输出每一行线段的颜色,然后输出每一列线段的颜色,否则输出 N O NO NO 。 R R R 代表红色, B B B 代表蓝色,其中行线段是指 从 ( i , j ) (i,j) (i,j) 到 ( i , j + 1 ) (i,j+1) (i,j+1) 的线段,列线段是指 从 ( i , j ) (i,j) (i,j) 到 ( i + 1 , j ) (i+1,j) (i+1,j) 的线段。存在输出 Y E S YES YES ,否则输出 N O NO NO 。
思路:
可以想到当
k
<
n
+
m
−
2
(
从
(
1
,
1
)
到
(
n
,
m
)
最小线段数
)
k < n+m-2(从 (1,1) 到 (n,m) 最小线段数)
k<n+m−2(从(1,1)到(n,m)最小线段数) 时不存在。题目重点所说,一个点可以多次经过,然后开始观察
当存在一个环就可以无限刷
4
4
4 的倍数的步数,所有偶数
m
o
d
4
mod 4
mod4 结果不是
0
0
0 就是
2
2
2 ,因此要多设置一个弯让路线多走两步,由此可以得出结论,当
k
−
(
n
+
m
−
2
)
k-(n+m-2)
k−(n+m−2) 为奇数,一定无解,为偶数,只要在最短路线上构造一个环和一个弯即可。
这是官方题解的构造,在起点设一个环,在终点设一个弯。我不同于官方题解的构造。
代码:
#include <bits/stdc++.h>
#define endl '\n'
#define endy {cout<<"YES\n";}//return ;}❤
#define endn {cout<<"NO\n";}//return ;}❤
using namespace std;
typedef long long ll;
ll n, m, k, x, y;
void solve()
{
cin >> n >> m >> k;
ll tmp = n + m - 2;
if (k < tmp || (k - tmp) & 1) {
endn return;
}
endy for (int i = 1; i <= n; i++)
{
for (int j = 1; j < m; j++) {
if (j & 1)
cout << 'R' << ' ';
else
cout << 'B' << ' ';
}
cout << endl;
}
char ch[] = { 'B', 'R' };
if (m & 1)
swap(ch[0], ch[1]);
for (int i = 1; i < n; i++) {
for (int j = 1; j <= m; j++) {
if (m & 1) {
if (i % 2 == 0 && j == m)
cout << ch[0] << ' ';
else
cout << ch[1] << ' ';
} else {
if (i % 2 == 0 && j == m)
cout << ch[1] << ' ';
else
cout << ch[0] << ' ';
}
}
cout << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
// cout.tie(0);
ll _;
cin >> _;
while (_--)
solve();
return 0;
}
D. Absolute Beauty(思维/贪心)
题意:
给定长度为 n n n 数字序列 a a a 和 b b b ,问只交换一次 b i b_i bi 和 b j b_j bj 得到的 Σ ∣ a i − b i ∣ Σ|a_i-b_i| Σ∣ai−bi∣ 最大。
思路:
我们只关心怎样情况下的交换会引起增量的变化,把
∣
a
i
−
b
i
∣
|a_i-b_i|
∣ai−bi∣ 和
∣
a
j
−
b
j
∣
|a_j-b_j|
∣aj−bj∣ 看作两条线段,我们发现只有以下三种情况(官方题解的图)
可以发现,我们只需要让左边的线段的右端点尽可能往左,右边线段的左端点尽可能往右,这时候的增量肯定是最大的。这种时候就有以下四种情况。
可以发现只需要维护
a
i
≤
b
i
a_i≤b_i
ai≤bi 的
m
i
n
(
b
)
、
m
a
x
(
a
)
min(b) 、max(a)
min(b)、max(a) 和
b
i
≤
a
i
b_i≤a_i
bi≤ai 的
m
i
n
(
a
)
、
m
a
x
(
b
)
min(a) 、max(b)
min(a)、max(b) 。即可求出答案。
代码:
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const ll INF_ = 0x3f3f3f3f3f3f3f3f;
ll n, m, k, ans = 0;
void solve()
{
cin >> n;
ll a_ma = 0, b_ma = 0;
ll a_mi = INF_, b_mi = INF_;
vector<ll> a(n + 1), b(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
ans = 0;
for (int i = 1; i <= n; i++) {
cin >> m;
ans += (int)abs(a[i] - m);
if (a[i] > a_ma && a[i] <= m)
a_ma = a[i];
if (a[i] < a_mi && a[i] >= m)
a_mi = a[i];
if (m < b_mi && a[i] <= m)
b_mi = m;
if (m > b_ma && a[i] >= m)
b_ma = m;
}
ll tmp = max(a_ma - b_mi, b_ma - a_mi);
ll tmp1 = max(b_ma - b_mi, a_ma - a_mi);
tmp = max(tmp, tmp1);
if (tmp > 0) {
ans += 2*tmp;
}
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
// cout.tie(0);
ll _; cin >> _;
while (_--) solve();
return 0;
}