Codeforces Round #792 (Div. 1 + Div. 2) A-E

2022-11-08,

Codeforces Round #792 (Div. 1 + Div. 2) A-E

A

题目

https://codeforces.com/contest/1684/problem/A

题解

思路

知识点:数学。

显然长度大于等于3的数字串的最小数位是完全可以通过这些操作留到最后。

长度等于2的数字串只可能是个位数字。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
string str;
cin >> str;
if (str.length() == 2) cout << str.back() << '\n';
else {
int ans = ~(1 << 31);
for (int i = 0;i < str.length();i++) ans = min(ans, str[i] - '0');
cout << ans << '\n';
}
}
return 0;
}

B

题目

https://codeforces.com/problemset/problem/1684/B

题解

思路

知识点:数学,构造。

容易得到三个式子

\[x &= ky + a\\
y &= mz + b\\
z &= nx + c\\
\]

其中 \(k,m,n \in \mathbb{N^+}\)。

通过代入得到

\[x = \frac{kmc + kb + a}{1 - kmn}
\]

由于 \(z \mod x = c\) ,因此有 \(x > c\) ,考虑 \(k = 1,m = 1,n = 0\) 得到 \(x = a+b+c , y = b+c , z = c\) 。

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>

using namespace std;

int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
long long a, b, c;
cin >> a >> b >> c;
cout << a + b + c << ' ' << b + c << ' ' << c << '\n';
}
return 0;
}

C

题目

https://codeforces.com/problemset/problem/1684/C

题解

思路

知识点:模拟,排序。

通过排序完数组和原来的对比数组,找到第一组两个在同一行需要交换位置的点,如果超过2个同一行的需要交换的点说明一次交换不可行直接输出-1,如果为0个则不需要交换,设置任意一个点作为两个交换的点(即不交换)。

在确认两个交换点之后,对原数组每行进行交换,每行交换之后判断是否是排序好的,如果有一行不是说明不可行输出-1,否则为答案。

时间复杂度 \(O(nm \log m)\)

空间复杂度 \(O(nm)\)

代码

#include <bits/stdc++.h>
#define ll long long using namespace std; bool solve() {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m)), b(n, vector<int>(m));
for (int i = 0;i < n;i++) {
for (int j = 0;j < m;j++) {
cin >> a[i][j], b[i][j] = a[i][j];
}
}
for (int i = 0;i < n;i++) {
sort(b[i].begin(), b[i].end());
}
int pos1 = -1, pos2 = -1;
for (int i = 0;i < n;i++) {
for (int j = 0;j < m;j++) {
if (a[i][j] != b[i][j]) {
if (!~pos1) pos1 = j;
else if (!~pos2) pos2 = j;
else return false;
}
}
if (~pos1) break;
}
if (!~pos1) pos2 = pos1 = 0;
for (int i = 0;i < n;i++) {
swap(a[i][pos1], a[i][pos2]);
if (!is_sorted(a[i].begin(), a[i].end())) return false;
}
cout << pos1 + 1 << ' ' << pos2 + 1 << '\n';
return true;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

D

题目

https://codeforces.com/contest/1684/problem/D

题解

思路

知识点:贪心(贪给我使劲贪)。

结论:跳过以 \(n-1-i-a[i]\) 排序的前 \(k\) 小值的点,或者说是 \(i+a[i]\) 排序的前 \(k\) 大值的点。

意识流证明:

\(n-1-i-a[i]\) 可以理解为跳过一个点后的代价改变量(\(i+a[i]\) ,即 \(a[i]-(n-1-i)\) 可以理解为跳过一个点的代价减少量),而只要选改变量最小的 \(k\) 个点,并且减去在每个跳过的点因为之前跳过点产生的额外代价(跳过的点不应该有代价)共 \(k(k-1)/2\) ,可以得到一个最小实际改变量(实际上一定存在一种方法使其小于 \(0\) ,这也说明了最优方法一定把 \(k\) 次跳过机会用完),加上改变量就能使总代价最小。

数学证明:微扰法。

假设方案不是跳过最小改变量的 \(k\) 个位置,则存在更优方案。

首先,存在某个位置 \(i\) 没有被跳过,某个位置 \(j\) 被跳过,且有 \(n-1-i-a[i]<n-1-j-a[j]\),即 \(i+a[i]>j+a[j]\) ,有两种情况\(i<j\) 和 \(i>j\) 。

当 \(i<j\) 时,从 \(a[j]\) 到 \(a[i]\) 会让 \(j-i\) 个位置代价加一,并且答案减去 \(a[i]\) 加上 \(a[j]\),又因为 \(i+a[i]>j+a[j]\) , 可以得到改变量 \(a[j] - a[i] + j - i < 0\) ,改变一定更优。\(i>j\) 的情况同理。

时间复杂度 \(O(n\log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
#define ll long long using namespace std; int a[200007]; bool solve() {
int n, k;
cin >> n >> k;
///显然k个跳点都选上是最小的必要条件
ll ans = -1LL * k * (k - 1) / 2;///每个跳过的点都要减掉之前跳过点产生的额外代价,因为跳过的点没有代价
for (int i = 0;i < n;i++) cin >> a[i], ans += a[i], a[i] = n - 1 - i - a[i];///n-i-a[i]代表跳过这个点相对于不跳过会产生多少代价
sort(a, a + n);///用于挑选前k个产生代价最小的点跳过
for (int i = 0;i < k;i++) ans += a[i];///跳过产生的代价变化
cout << ans << '\n';
return true;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

E

题目

https://codeforces.com/contest/1684/problem/E

题解

思路

知识点:数据结构,贪心。

MEX:序列中最小未出现的自然数(包括0)。

例如,\(MEX(\{ 0,1,2,4,5 \})=3\) , \(MEX(\{ 1,2,4,5 \}) = 0\) 。

观察\(DIFF - MEX\) 的结果,发现 \(MEX\) 数之前的数必然都出现,则种类数一定和 \(MEX\) 一样,因此结果就是大于 \(MEX\) 的数的种类数。

我们发现,当通过修改数字使得 \(MEX\) 增大时,\(DIFF\) 必然是不增的,因此可以先考虑在 \(k\) 次操作得到尽可能大的 \(MEX\) ,而数组长度为 \(n\) ,则必然可以通过修改使在 \(n\) 以内的所有 \(MEX\) 都能得到,所以可以从 \(0\) 开始遍历 ,若存在该数字则继续,否则通过一次操作得到这个数字,这样就可以得到 \(k\) 次操作后最大可能的 \(MEX\) 了,这样操作得到的 \(MEX\) 可能会大于 \(n\) 但不需要限制操作次数使其合法,因为大于等于 \(n\) 在之后处理中也都能得到正确结果 \(0\) 。

得到最大的 \(MEX\) 后,只要考虑操作比 \(MEX\) 大的就行(即使可能用不完 \(k\) 次),首先因为大于 \(MEX\) 的数字种类即是答案,所以优先取完其尽可能多的种类能直接降低答案,其次 \(MEX\) 是操作 \(k\) 次内的必然结果,无论如何都能在 \(k\) 次内达到,而大于 \(MEX\) 的数字种类即是答案,那么只考虑 \(k\) 次操作后(可能不到 \(k\) 次就取完了,剩下的操作可以理解为用小于 \(MEX\) 的数补全,从而也能达到 \(MEX\) ,但不改变答案 \(0\))这些数的种类即可。特别地,最大 \(MEX\) 在取的时候可能大于等于其最大可能值 \(n\) ,即最后大于 \(MEX\) 数的种类必然为\(0\) ,也即 \(k\) 次操作内必然取完大于 \(MEX\) 的数,答案也为 \(0\) ,所以在之前的操作不需要通过限制操作次数来限制 \(MEX\) 。

时间复杂度 \(O(n \log^2 n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
#define ll long long using namespace std; bool solve() {
int n, k;
cin >> n >> k;
map<int, int> m;///用于元素与数量映射
for (int i = 0;i < n;i++) {
int tmp;
cin >> tmp;
m[tmp]++;
}
int mex = 0;
for (int i = 0, kk = k;;i++) {
if (!m.count(i)) {
if (kk) kk--;
else {
mex = i;
break;
}
}
}
priority_queue<int, vector<int>, greater<int>> pq;
for (auto i : m) if (i.first > mex) pq.push(i.second);
while (!pq.empty() && k >= pq.top()) { ///答案等于大于mex的元素种类,所以k<最小个数元素时,不用继续,此种类补全k次后不会消失,留着就行
k -= pq.top();
pq.pop();
}
cout << pq.size() << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

Codeforces Round #792 (Div. 1 + Div. 2) A-E的相关教程结束。

《Codeforces Round #792 (Div. 1 + Div. 2) A-E.doc》

下载本文的Word格式文档,以方便收藏与打印。