Codeforces Round #798 (Div. 2)
A. Lex String
题意
给定长度为的串和长度为的串,保证没有字符同时出现在两个串中。
有一个初始为空的串,支持以下两种操作:
- 从中选一个字符,将其从中删除,然后加到的末尾。
- 从中选一个字符,将其从中删除,然后加到的末尾。
还有一个限制:同一个操作不能连续使用超过次。
要求不断操作至和至少有一个空串,问字典序最小的。
其中。
思路
因为要字典序最小,肯定就是贪心先加小的字符。
不妨先给和,排个序,然后就是一个类似归并排序合并的过程。
合并过程中记录上一次使用的是哪一个操作,这个操作连续使用了多少次,如果当期最优的操作不满足条件就使用另外一个操作。
模拟一下完事。
AC代码
// Problem: A. Lex String
// Contest: Codeforces - Codeforces Round #798 (Div. 2)
// URL: https://codeforces.com/contest/1689/problem/A
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
int n, m, k;
std::cin >> n >> m >> k;
std::string a, b;
std::cin >> a;
std::cin >> b;
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
std::string c;
int pa = 0, pb = 0, x = 0, y = -1;
while (pa < n && pb < m) {
int z = 0;
if (a[pa] < b[pb]) {
z = 0;
} else {
z = 1;
}
if (y == z && x == k)
z ^= 1;
if (z == 0) {
c += a[pa++];
} else {
c += b[pb++];
}
if (z == y) {
++x;
} else {
x = 1;
y = z;
}
}
std::cout << c << "\n";
}
B. Mystic Permutation
题意
给定一个长度为的排列,问满足下列条件且字典序最小的排序:
- 。
其中。
思路
可能最优的答案是,但是这个排列可能存在不满足条件的位置。
从左至右遍历所有位置,当前位置不满足条件时通过调整使其满足条件,具体调整就是时交换和,时交换和。
正确性没太仔细证,大概就是每次调整都会使字典序变大,然后每次贪心使用了字典序增加最小的操作,且前面的操作权重更大。
AC代码
// Problem: B. Mystic Permutation
// Contest: Codeforces - Codeforces Round #798 (Div. 2)
// URL: https://codeforces.com/contest/1689/problem/B
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
--a[i];
}
if (n == 1) {
std::cout << "-1\n";
return;
}
std::vector<int> b(n);
std::iota(b.begin(), b.end(), 0);
for (int i = 0; i < n; ++i) {
if (b[i] == a[i]) {
if (i == n - 1) {
std::swap(b[i], b[i - 1]);
} else {
std::swap(b[i], b[i + 1]);
}
}
}
for (int i = 0; i < n; ++i)
std::cout << b[i] + 1 << " \n"[i + 1 == n];
}
C. Infected Tree
题意
给定一颗棵节点的二叉树,树以为根。
初始时,树的根节点被病毒感染了。
接下来的过程会重复次:
- 你可以选择一个节点,删除节点及所有一端是的边;你也可以选择不操作。
- 被感染的点会扩散病毒,对于一个被感染的点,和它邻接的点都会被感染;已经被感染的点保持会保持被感染。
问你最多能保多少个点不被感染且不被删除。
其中。
思路
树形DP。
令表示已经被感染了,通过后续操作的子树中最多能保多少个点。显然即为答案。
如果是叶子,则。
如果只有一个儿子,则。
如果有两个儿子,分别记为,则。
然后就是DFS跑一炮算答案了。
AC代码
// Problem: C. Infected Tree
// Contest: Codeforces - Codeforces Round #798 (Div. 2)
// URL: https://codeforces.com/contest/1689/problem/C
// Memory Limit: 256 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
int n;
std::cin >> n;
std::vector<std::vector<int>> g(n);
for (int i = 0; i < n - 1; ++i) {
int u, v;
std::cin >> u >> v;
--u, --v;
g[u].push_back(v);
g[v].push_back(u);
}
std::vector<int> sz(n, 0);
std::function<void(int, int)> dfs_size = [&](int u, int fa) {
sz[u] = 1;
for (int v : g[u]) {
if (v == fa)
continue;
dfs_size(v, u);
sz[u] += sz[v];
}
};
dfs_size(0, -1);
std::vector<int> dp(n, 0);
std::function<void(int, int)> dfs_ans = [&](int u, int fa) {
std::vector<int> a;
for (int v : g[u]) {
if (v == fa)
continue;
dfs_ans(v, u);
a.push_back(v);
}
if (a.size() == 0) {
dp[u] = 0;
} else if (a.size() == 1) {
dp[u] = sz[a[0]] - 1;
} else {
dp[u] = std::max(dp[a[0]] + sz[a[1]] - 1, dp[a[1]] + sz[a[0]] - 1);
}
};
dfs_ans(0, -1);
std::cout << dp[0] << "\n";
}
D. Lena and Matrix
题意
给定一个行列的矩阵,矩形的每一个元素是黑色或者白色的,要求求出一个点最小化
其中。
思路1
对于一个点,离他最远的黑点可能在他的左上,右上,左下或者右下,而每个方向上的最远的黑点是可以通过DP做到时间计算的。
然后再枚举一遍所有点就可以得出答案。
思路2
把要求最小化的式子分类讨论一下,可以得到:
然后就可以发现:所有黑点中只有4个是有效的,分别是最小化4个式子后半部分的点。
先遍历一遍求出4个有效点,然后在遍历一遍算答案就行了。
思路1 AC代码
// Problem: D. Lena and Matrix
// Contest: Codeforces - Codeforces Round #798 (Div. 2)
// URL: https://codeforces.com/contest/1689/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
const int N = 1005;
int n, m;
char s[N][N];
int dp1[N][N], dp2[N][N], dp3[N][N], dp4[N][N];
void solve_case(int Case) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
std::cin >> s[i] + 1;
for (int i = 0; i <= n + 1; ++i) {
for (int j = 0; j <= m + 1; ++j) {
dp1[i][j] = dp2[i][j] = dp3[i][j] = dp4[i][j] = -1;
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i][j] == 'B')
dp1[i][j] = 0;
if (dp1[i - 1][j] != -1)
dp1[i][j] = std::max(dp1[i][j], dp1[i - 1][j] + 1);
if (dp1[i][j - 1] != -1)
dp1[i][j] = std::max(dp1[i][j], dp1[i][j - 1] + 1);
if (dp1[i - 1][j - 1] != -1)
dp1[i][j] = std::max(dp1[i][j], dp1[i - 1][j - 1] + 2);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= 1; --j) {
if (s[i][j] == 'B')
dp2[i][j] = 0;
if (dp2[i - 1][j] != -1)
dp2[i][j] = std::max(dp2[i][j], dp2[i - 1][j] + 1);
if (dp2[i][j + 1] != -1)
dp2[i][j] = std::max(dp2[i][j], dp2[i][j + 1] + 1);
if (dp2[i - 1][j + 1] != -1)
dp2[i][j] = std::max(dp2[i][j], dp2[i - 1][j + 1] + 2);
}
}
for (int i = n; i >= 1; --i) {
for (int j = 1; j <= m; ++j) {
if (s[i][j] == 'B')
dp3[i][j] = 0;
if (dp3[i + 1][j] != -1)
dp3[i][j] = std::max(dp3[i][j], dp3[i + 1][j] + 1);
if (dp3[i][j - 1] != -1)
dp3[i][j] = std::max(dp3[i][j], dp3[i][j - 1] + 1);
if (dp3[i + 1][j - 1] != -1)
dp3[i][j] = std::max(dp3[i][j], dp3[i + 1][j - 1] + 2);
}
}
for (int i = n; i >= 1; --i) {
for (int j = m; j >= 1; --j) {
if (s[i][j] == 'B')
dp4[i][j] = 0;
if (dp4[i + 1][j] != -1)
dp4[i][j] = std::max(dp4[i][j], dp4[i + 1][j] + 1);
if (dp4[i][j + 1] != -1)
dp4[i][j] = std::max(dp4[i][j], dp4[i][j + 1] + 1);
if (dp4[i + 1][j + 1] != -1)
dp4[i][j] = std::max(dp4[i][j], dp4[i + 1][j + 1] + 2);
}
}
int min_dist = INT_MAX, x, y;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int dist = std::max({dp1[i][j], dp2[i][j], dp3[i][j], dp4[i][j]});
if (dist < min_dist) {
x = i, y = j;
min_dist = dist;
}
}
}
std::cout << x << " " << y << "\n";
}
E. ANDfinity
题意
给定一个长度为的数组,有以下两种操作:
- 选一个,然后将其加1。
- 选一个,然后将其减1。
根据去建图:如果就在和之间连一条边。
问最少的操作次数使得建出来的图是连通的。
其中。
思路
首先肯定是孤立点,不妨先对所有这种点加1。
观察可得:此时至多还需要两次操作就可以使图连通。
证明:记为的least significant bit。记,有个点满足。时令减1即可;否则选择将其减1,选择将其加1,则的点都与连通,与连通,与连通。由此,通过至多两次操作就一定可以使图连通。
对于固定的判断建出来的图是否连通可以借助并查集做,然后分类讨论:
- 如果初始的时候就已经连通了,那就不需要再操作了。复杂度。
- 只需要操作1次,就枚举这个操作是什么,看操作完是否连通。操作有种,每次判断,复杂度共。
- 需要两次操作,就按照证明里的步骤去搞。复杂度。
总的时间复杂度为。
AC代码
// Problem: E. ANDfinity
// Contest: Codeforces - Codeforces Round #798 (Div. 2)
// URL: https://codeforces.com/contest/1689/problem/E
// Memory Limit: 256 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i)
std::cin >> a[i];
int ans = 0;
for (int i = 0; i < n; ++i) {
if (a[i] == 0) {
++ans;
a[i] = 1;
}
}
auto connected = [&]() -> bool {
std::vector<int> f(n + 32), sz(n + 32);
std::function<int(int)> find = [&](int x) { return x == f[x] ? x : f[x] = find(f[x]); };
auto merge = [&](int x, int y) {
x = find(x), y = find(y);
if (x == y)
return;
f[x] = y;
sz[y] += sz[x];
};
for (int i = 0; i < n + 32; ++i) {
f[i] = i;
sz[i] = i < n ? 1 : 0;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 30; ++j) {
if ((a[i] >> j) & 1) {
merge(i, n + j);
}
}
}
return sz[find(0)] == n;
};
if (!connected()) {
bool flag = false;
for (int i = 0; i < n; ++i) {
a[i] = a[i] - 1;
if (connected()) {
flag = true;
++ans;
break;
}
a[i] = a[i] + 1;
}
if (!flag) {
for (int i = 0; i < n; ++i) {
a[i] = a[i] + 1;
if (connected()) {
flag = true;
++ans;
break;
}
a[i] = a[i] - 1;
}
}
if (flag) {
;
} else {
int max_low_bit = 0;
for (int i = 0; i < n; ++i) {
max_low_bit = std::max(max_low_bit, (a[i] & -a[i]));
}
for (int i = 0; i < n; ++i) {
if ((a[i] & -a[i]) == max_low_bit) {
--a[i];
++ans;
break;
}
}
for (int i = 0; i < n; ++i) {
if ((a[i] & -a[i]) == max_low_bit) {
++a[i];
++ans;
break;
}
}
}
}
std::cout << ans << "\n";
for (int i = 0; i < n; ++i)
std::cout << a[i] << " \n"[i + 1 == n];
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2021-06-11 Codeforces Round #725 (Div. 3) 题解(A-G)