Codeforces Round #710 (Div. 3) 题解 A~F
(为什么只有A~F,因为G我不会)
A. Strange Table
题意
给出一个
n
×
m
n\times m
n×m矩阵,按列从小到大标号,再将该矩阵从小到大按行标号,求在按列标号时,序号
x
x
x的元素,在按行标号时序号为多少
1
4
7
10
13
2
5
8
11
14
3
6
9
12
15
⇒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\begin{matrix} 1 & 4 & 7 & 10 & 13\\ 2 & 5 & 8 & 11 & 14\\ 3 & 6 & 9 & 12 & 15 \end{matrix} \ \Rightarrow\ \begin{matrix} 1 & 2 & 3 & 4 & 5\\ 6 & 7 & 8 & 9 & 10\\ 11 & 12 & 13 & 14 & 15 \end{matrix}
123456789101112131415 ⇒ 161127123813491451015
思路
根据矩阵的大小,由 x x x可以推出其在矩阵中的坐标,进而根据逆转的坐标推得新的序号
时间复杂度
O ( 1 ) O(1) O(1)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| A - Strange Table | GNU C++17 | Accepted | 92 ms | 0 KB |
#include <bits/stdc++.h>
using namespace std;
int main() {
// freopen("in.txt", "r", stdin);
long long t, n, m, p, x, y;
cin >> t;
while (t--) {
cin >> n >> m >> p;
x = (p - 1) / n;//位于x行
y = (p - 1) % n + 1;//位于y列
cout << m*(y - 1) + x + 1 << endl;
}
return 0;
}
B. Partial Replacement
题意
给定一个由’.‘和’*‘组成的长度为 n n n字符串,将其中的某些’*‘用’x’替代,且要求头尾的’*‘必须被替代为’x’,且两个’x’之间的距离(下标之差)不得超过给定的 k k k
思路
典型的贪心问题,从第一个’*‘开始,将其替换为’x’,然后在其后不多于 k k k的长度内寻找最后一个’*’,将这个’*‘替换为’x’,再进行下一次查找,直到找到最后一个’*’
时间复杂度
O ( n ) O(n) O(n)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| B - Partial Replacement | GNU C++17 | Accepted | 31 ms | 0 KB |
#include <bits/stdc++.h>
using namespace std;
int main() {
// freopen("in.txt", "r", stdin);
int t, n, k, i, j, tmp, l, r, cnt;
string s;
cin >> t;
while (t--) {
cin >> n >> k;
cin >> s;
cnt = 0;
l = 0;
while (s[l] != '*')
++l;
r = n - 1;
while (s[r] != '*')
--r;
tmp = j = i = l;
++cnt;
s[i] = 'x';
while (j <= r) {
while (j - i <= k && j <= r) {
if (s[j] == '*')
tmp = j;//tmp保存最后一个'*'
++j;
}
i = tmp;
if (s[tmp] != 'x') {//考虑到最后一个'*'已经被替换为'x'的情况,需要判断一下
s[tmp] = 'x';
++cnt;
}
}
cout << cnt << endl;
}
return 0;
}
C. Double-ended Strings
题意
给定两个字符串 a a a和 b b b,每次操作允许在字符串的头尾删除一个字符(只要字符串非空),求能使得两个字符串相等的最小操作数
思路
操作后得到的新字符串一定是原字符串连续的一部分,而新字符串相等意味着原字符串中存在一段连续的字符是相等的,问题转化为求原字符串中最长的公共连续段,需要的操作数为总字符数减去两倍的最长公共连续段的长度( ∣ s a ∣ + ∣ s b ∣ − 2 × ∣ s p u b l i c ∣ |s_a|+|s_b|-2\times|s_{public}| ∣sa∣+∣sb∣−2×∣spublic∣)。此时我们可以采用移动字符串的方法,例如原字符串为 a b c d b c \begin{matrix}a\ b\ c\ d\\b\ c\ \ \ \ \ \ \end{matrix} a b c db c ,此时直接一一比较,发现 a ≠ b a\not= b a=b, b ≠ c b\not= c b=c,没有相同的段(最长公共连续段长度为0)。我们换一种对齐方式,可以变成 a b c d b c \begin{matrix}a\ b\ c\ d\\b\ c\end{matrix} a b c db c,此时b和c是相同的,此时最长公共段长度为2。枚举所有可能的对齐方式,我们一定能找到最大的最长公共段长度,也就得到了最小操作数。需要注意的是,两个字符串在对齐时不一定满足包含关系,即存在 a b c c d e \begin{matrix}a\ b\ c\\\ \ \ \ \ \ \ \ \ \ \ \ c\ d\ e\end{matrix} a b c c d e这样的对齐方式
时间复杂度
O ( n 2 ) O(n^2) O(n2)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| C - Double-ended Strings | GNU C++17 | Accepted | 15 ms | 0 KB |
#include <bits/stdc++.h>
using namespace std;
int main() {
// freopen("in.txt", "r", stdin);
int t, i, j, k, maxm, res, cnt;
string s1, s2;
cin >> t;
while (t--) {
cin >> s1;
cin >> s2;
maxm = 0;//用于保存所有对齐方式中最大的最长公共连续段长度
i = 0;//s1固定不动,s2移动
for (j = 0; j < s2.length(); ++j) {
res = 0;//用于保存当前对齐方式下最长公共连续段
cnt = 0;//用于计数当前位置的公共连续段
for (k = 0; k + i < s1.length() && k + j < s2.length(); ++k) {
if (s1[k + i] != s2[k + j])
cnt = 0;
else
++cnt;
res = max(res, cnt);
}
maxm = max(maxm, res);
}
j = 0;//s2固定不动,s1移动
for (i = 0; i < s1.length(); ++i) {
res = 0;
cnt = 0;
for (k = 0; k + i < s1.length() && k + j < s2.length(); ++k) {
if (s1[k + i] != s2[k + j])
cnt = 0;
else
++cnt;
res = max(res, cnt);
}
maxm = max(maxm, res);
}
cout << s1.length() + s2.length() - 2 * maxm << endl;
}
return 0;
}
D. Epic Transformation
题意
给定一个长为 n n n数组 a a a,现允许对 a a a进行操作,每次选定 a a a中两个不同的元素,并将它们删除,操作次数不限,求操作结束后数组 a a a剩余的元素数的最小值
思路
我们只需关注数组 a a a中出现次数最多的元素(假设其出现次数记作 m m m)。如果 m ≥ n 2 m\ge\frac{n}{2} m≥2n,则说明该元素最终会剩余,最小剩余数为 m − ( n − m ) m-(n-m) m−(n−m),化简得 2 × m − n 2\times m-n 2×m−n。如果 m < n 2 m<\frac{n}{2} m<2n,那么该元素可以被删除完,由于每次必须删除两个元素,当 n n n为奇数时,一定会有一个元素剩余,因此只需输出 n n n的奇偶性即可,即 n % 2 n\%2 n%2
时间复杂度
O ( n ) O(n) O(n)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| D - Epic Transformation | GNU C++17 | Accepted | 358 ms | 3300 KB |
#include <bits/stdc++.h>
using namespace std;
map<int, int> m;
int main() {
// freopen("in.txt", "r", stdin);
int t, n, i, j, k, maxm;
cin >> t;
while (t--) {
cin >> n;
m.clear();
for (i = 0; i < n; ++i) {
cin >> j;
++m[j];//利用map进行数组元素出现次数的统计
}
maxm = 0;
for (auto it : m) {
if (it.second > maxm)
maxm = it.second;//遍历查找最大出现次数
}
if (maxm <= n - maxm)
cout << n % 2 << endl;
else
cout << 2 * maxm - n << endl;
}
return 0;
}
E. Restoring the Permutation
题意
定义数列 q q q为排列 p p p的前缀最大值,也就是说, q i = m a x ( p 1 , p 2 , … , p i ) q_i=max(p_1,p_2,\dots,p_i) qi=max(p1,p2,…,pi)。现给出数列 q q q,求可能的排列 p p p中字典序最大和最小的项
思路
首先可以肯定,当 q i ≠ q i − 1 q_i\not=q_{i-1} qi=qi−1或 i = 1 i=1 i=1时, p i = q i p_i=q_i pi=qi(我称之为定值点)。当 i i i取其他值时,满足 p i < q i p_i<q_i pi<qi(我称之为不定值点)。因此,无论是最小序列还是最大序列,定值点的值已经确定,对于最小序列,只需将其余没有出现过的数从小到大依次填入不定值点即可,而对于最大序列,需要从左到右依次填入小于 q i q_i qi且没有出现过的最大值。最小序列的实现较容易,可以通过数组以 O ( n ) O(n) O(n)复杂度实现,但对于最大序列,如果使用数组遍历来实现,在最恶劣情况下其复杂度会退化到 O ( n 2 ) O(n^2) O(n2),引起超时,正确解法是使用set的upper_bound / lower_bound来寻找有上限的最大值,单次复杂度 O ( l o g 2 n ) O(log_2n) O(log2n),总复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),避免了超时。
时间复杂度
O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| E - Restoring the Permutation | GNU C++17 | Accepted | 421 ms | 15100 KB |
#include <bits/stdc++.h>
using namespace std;
int a[200005], b[200005];
int maxm[200005];
set<int> s1, s2;
int main() {
// freopen("in.txt", "r", stdin);
int t, n, i, j, k;
cin >> t;
while (t--) {
cin >> n;
s1.clear();
s2.clear();
for (i = 0; i < n; ++i) {
cin >> maxm[i];
s1.insert(i + 1);//这边使用了两个set,其实一个set也可以完成
s2.insert(i + 1);
}
i = j = 0;
while (i < n) {
while (j < n && maxm[j] == maxm[i])
++j;
a[i] = b[i] = maxm[i];//定值点直接赋值
s1.erase(maxm[i]);
s2.erase(maxm[i]);//删除已经出现过的元素,这点不能忘记
auto it1 = s1.begin();
auto it2 = s2.lower_bound(maxm[i]);//算法核心,找到小于等于目标值的第一个元素
if (it2 != s2.begin())//如果n=1,这里的--操作会引起RE,需要判定一下
--it2;
for (k = i + 1; k < j; ++k) {//不定值点用循环依次求解
a[k] = *it1;
++it1;
s1.erase(a[k]);
b[k] = *it2;
--it2;//先移动迭代器再删除元素
s2.erase(b[k]);//用完也要记得删除
}
i = j;
}
for (i = 0; i < n; ++i)
cout << a[i] << ' ';
cout << endl;
for (i = 0; i < n; ++i)
cout << b[i] << ' ';
cout << endl;
}
return 0;
}
F. Triangular Paths
题意
给出一个无限大的图,该图由无限多个点按照金字塔形组成,从上到下第 r r r层有 r r r个点,每一层中从左到右依次标号为 c = 1 ∼ r c=1\sim r c=1∼r,用 ( r , c ) (r,c) (r,c)来表示第 r r r行第 c c c个点。初始状态下,对于 r + c r+c r+c是偶数的点, ( r , c ) (r,c) (r,c)有单向边连向其左儿子 ( r + 1 , c ) (r+1,c) (r+1,c),而对于 r + c r+c r+c是奇数的点, ( r , c ) (r,c) (r,c)有单向边连向其右儿子 ( r + 1 , c + 1 ) (r+1,c+1) (r+1,c+1)。现允许对任意点进行操作,使得该点改变指向其儿子的单向边的方向(原本连左儿子就改成右儿子,原本连右儿子就改成左儿子)。每次给出 n n n个点,要求从 ( 1 , 1 ) (1,1) (1,1)开始,通过对点进行任意次数操作,找到一条经过所有给定的点的路径,求操作的最小次数,题目保证存在一个合法的路径
思路
首先找规律,初始状态下, r + c r+c r+c为偶数的点(下称偶数点),一定指向一个 r + c r+c r+c为奇数的点(下称奇数点),而奇数点,依然指向奇数点。由于 r + c r+c r+c与 r − c r-c r−c的奇偶性是相同的,且 r > c r>c r>c,因此以下我们研究 r − c r-c r−c的性质。如果我们观察从 ( 1 , 1 ) (1,1) (1,1)到 ( r , 1 ) (r,1) (r,1)的一系列点,不难发现,每往下一层, r − c r-c r−c增加1,点的奇偶性改变一次。由于图中都是单向边,因此往下一层后不可能返回,我们需要首先对所有的目标点按照 r r r从小到大排序。对于从 ( r 1 , c 1 ) (r_1,c_1) (r1,c1)到 ( r 2 , c 2 ) (r_2,c_2) (r2,c2)的移动过程,只要在 [ r 1 − c 1 , r 2 − c 2 ] [r_1-c_1,r_2-c_2] [r1−c1,r2−c2]区间内存在一个奇数值,就能不经过任何操作,在不改变 r − c r-c r−c的情况下增大 c c c的值,此时只需要计算从 r 1 − c 1 r_1-c_1 r1−c1到 r 2 − c 2 r_2-c_2 r2−c2的过程中,经过了多少奇数值,因为在奇数值处需要改变边的方向。若该奇数值不存在,则意味着 r 1 − c 1 = r 2 − c 2 r_1-c_1=r_2-c_2 r1−c1=r2−c2,且均为偶数,那么在从 r 1 r_1 r1到 r 2 r_2 r2的变化过程中,每一次都需要改变边的方向
时间复杂度
O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
AC代码
| Problem | Lang | Verdict | Time | Memory |
|---|---|---|---|---|
| F - Triangular Paths | GNU C++17 | Accepted | 685 ms | 1600 KB |
#include <bits/stdc++.h>
using namespace std;
struct pt {
int x, y;
} p[200005];
int cmp(pt a, pt b) {
return a.x < b.x;
}
int main() {
// freopen("in.txt", "r", stdin);
int t, n, i, j, k, sum;
p[0] = {1, 1};//开始在(1,1),预先加入
cin >> t;
while (t--) {
cin >> n;
for (i = 1; i <= n; ++i) {
cin >> p[i].x;
}
for (i = 1; i <= n; ++i) {
cin >> p[i].y;
}
sort(p + 1, p + n + 1, cmp);//先排序
sum = 0;
for (i = 1; i <= n; ++i) {
j = p[i - 1].x - p[i - 1].y;//r1-c1
k = p[i].x - p[i].y;//r2-c2
if (j == k) {
if (j % 2 == 0)//相等且为偶数,每次都要操作(如果是奇数直接为0)
sum += p[i].x - p[i - 1].x;
} else {
if (j % 2 == 0 && k % 2 != 0)//分类讨论计算要经过多少个奇数点
sum += (k - j) / 2;
else
sum += (k - j + 1) / 2;
}
}
cout << sum << endl;
}
return 0;
}
1825





