[题解] 2022 CCPC 河南省赛 部分题解 2022 CCPC Henan Provincial Collegiate Programming Contest
正赛榜单:2022年河南省CCPC大学生程序设计竞赛 | RankLand
题目链接:Dashboard - 2022 CCPC Henan Provincial Collegiate Programming Contest - Codeforces
官方题解:CCPC Henan Provincial Contest 2022 Solution
A
签到,注意没有前导0,0到了 1 的后面。
Code
时间复杂度 \(O(1)\)。
int n;
cin >> n;
if (n > 10) {
cout << -1 << '\n';
} else {
vector<int> t = {1, 0, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < n; i++) {
cout << t[i];
}
cout << '\n';
}
B
首先转化,'a' = 1, 'e' = 2, 'h' = 3, 'n' = 4。
注意到对哈希值求和之后是没有取模的,我们可以利用这点,想办法让分割出来的子串哈希值贴近 模数。
最极端的情况,长度为 7 的串,第一位是 1,第二位<= 3,其余的串长度最大为 6。写了个 dp 没过。
注意到可以凹,即:超过了 7 位虽然会被模掉,但是也有可能 把哈希值凹的更高,但是如果超过 一个位数,一定是拆成更小的串更优,我们不妨尝试一下 20(实际上这个数在题解中证明了是 16)。
预处理整个串的哈希值,在 dp 的时候边转移边计算哈希。
dp 的时候注意到原字符串是个环,我们需要 转动几次这个串。
Code
设最长子串长度为 \(k\) ,时间复杂度 \(O(nk^2)\)。一个 k 是转动,一个 k 是转移。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
const int maxn = 2e5 + 5;
const int mod = 998244353;
int n, k;
vector<int> a;
i64 work() {
vector<i64> dp(n + 2, -1e18);
dp[n + 1] = 0;
for (int i = n; i >= 1; i--) {
i64 cur = 0;
for (int j = 1; i + j <= min(n + 1, i + k); j++) {
cur = (cur * 31 + a[i + j - 1]) % mod;
dp[i] = max(dp[i], dp[i + j] + cur);
}
}
return dp[1];
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
string s;
cin >> s;
n = s.size();
k = min(20, n);
a.resize(n + 2);
for (int i = 0; i < n; i++) {
int t = 0;
if (s[i] == 'a') t = 1;
if (s[i] == 'e') t = 2;
if (s[i] == 'h') t = 3;
if (s[i] == 'n') t = 4;
a[i + 1] = t;
}
i64 ans = 0;
for (int i = 1; i <= k + 1; i++) {
i64 res = work();
ans = max(ans, res);
vector<int> b = a;
for (int j = 1; j < n; j++) {
a[j] = b[j + 1];
}
a[n] = b[1];
}
cout << ans << '\n';
return 0;
}
E
笔者读完题没想太多,写了一个很糖的 dp(01背包并记录下通过哪个字符转移).
一定有更简单的写法,这里网上有很多,不再赘述。
Code
时间复杂度 \(O(26*n)\) 。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
vector<vector<int>> dp(3, vector<int>(26));
vector<vector<char>> vp(3, vector<char>(26));
for (int i = 1; i <= n; i++) {
vector tp = dp;
int x = s[i] - 'a';
if (tp[2][x] >= 1) {
dp[2][x]++;
} else {
for (int j = 0; j < 26; j++) {
if (tp[1][j] >= 7) {
dp[2][x] = 1;
vp[2][x] = 'a' + j;
break;
}
}
}
if (dp[1][x] >= 1) {
dp[1][x]++;
} else {
for (int j = 0; j < 26; j++) {
if (tp[0][j] >= 5) {
dp[1][x] = 1;
vp[1][x] = 'a' + j;
break;
}
}
}
dp[0][x]++;
}
int ok = 0;
vector<char> ans;
for (int i = 0; i < 26; i++) {
if (dp[2][i] >= 5) {
ok = 1;
ans.push_back('a' + i);
ans.push_back(vp[2][i]);
ans.push_back(vp[1][vp[2][i] - 'a']);
break;
}
}
if (!ok) {
cout << "none" << '\n';
return 0;
}
for (int i = 1; i <= 5; i++) {
cout << ans[2];
}
for (int i = 1; i <= 7; i++) {
cout << ans[1];
}
for (int i = 1; i <= 5; i++) {
cout << ans[0];
}
cout << '\n';
return 0;
}
F
注意到 \(A = \{ 0, 1, 2, 3, ..., k \}\ (k >= 0)\) 这种构造是可以构造出任意奇数的,因为 \(A + A= \{ 0, 1, 2, 3, ...,2k \}\) 的大小是 \(2k + 1\)。
又因为 \(A = \{ 0, 2, 3, 4, 5,....,k \}\ (k >= 3)\) 这种构造,使得 \(1 \notin (A + A)\) 。其他数都是存在的,所以这样可以构造出 \(>= 6\) 的偶数,大小是 \(2k\)。
\(2\) 和 \(4\) 不能被构造出,因为 \(|A|=2\) 时,\(|A+A| = 3\);\(|A| = 3\) 时,\(5<=|A+A|<=6\)。\(|A|\) 再大,答案就更大了。
Code
时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
if (n == 2 || n == 4) {
cout << -1 << '\n';
return 0;
}
if (n % 2) {
cout << n / 2 + 1 << '\n';
for (int i = 0; i <= n / 2; i++) {
cout << i << ' ';
}
} else {
cout << n / 2 << '\n';
cout << 0 << ' ';
for (int i = 2; i <= n / 2; i++) {
cout << i << ' ';
}
}
return 0;
}
G
诈骗题,我以为题读假了,单开根本不敢写。中期队友读了也没问题才战战兢兢的交,过完之后人傻了。
根据题意,都是同一列的进行 & 运算,所以根本不影响最终的每一列的 & 运算。
直接把所有的字符串 & 之后输出即可。所有操作均无效。
Code
时间复杂度 \(O(nm)\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> a(m, 1);
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
for (int j = 0; j < m; j++) {
a[j] &= s[j] - '0';
}
}
int Q;
cin >> Q;
while (Q--) {
int i, j, l, r, p;
cin >> i >> j >> l >> r >> p;
}
cout << accumulate(a.begin(), a.end(), 0) << '\n';
return 0;
}
H
这个题必须用 回溯 做,不回溯做不了。
不回溯 做不了的原因:
假设刚出起点遇到 L 型 水管,我们必须要做出一个 向左 或者 向右 的决策,如果不回溯,那么这个决策丢掉了,会走上错的路,对结果造成影响。
终点处出现 L 型水管同理。
值得注意的是,只有这 2 个地方能决策,剩下的地方 I 型水管 和 L 型水管都只有一个选择,这也是回溯能过的原因。
Code
回溯用 DFS 比较好实现。时间复杂度\(O(n)\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
int n, X, Y;
vector<vector<int>> vis;
vector<vector<char>> a;
// 0 上 | 1 右 | 2 下 | 3 左
bool dfs(int x, int y, int dir) {
if (x == 4 && y == Y) {
return 1;
}
if (x < 1 || x > 4 || y < 1 || y > n || vis[x][y] || a[x][y] == 0) {
return 0;
}
vis[x][y] = 1;
int ok = 0;
if (a[x][y] == 'I') {
if (dir == 0) {
ok = dfs(x - 1, y, dir);
} else if (dir == 1) {
ok = dfs(x, y + 1, dir);
} else if (dir == 2) {
ok = dfs(x + 1, y, dir);
} else {
ok = dfs(x, y - 1, dir);
}
} else {
if (dir == 0 || dir == 2) {
ok = dfs(x, y + 1, 1) | dfs(x, y - 1, 3);
} else {
ok = dfs(x - 1, y, 0) | dfs(x + 1, y, 2);
}
}
vis[x][y] = 0;
return ok;
}
void solve() {
cin >> n >> X >> Y;
vis.assign(5, vector<int>(n + 1));
a.assign(5, vector<char>(n + 1));
for (int i = 1; i <= n; i++) {
cin >> a[2][i];
}
for (int i = 1; i <= n; i++) {
cin >> a[3][i];
}
a[1][X] = 'I';
int ok = dfs(1, X, 2);
cout << (ok ? "YES" : "NO") << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
J
这种一次性求很多解的题,不是预处理,就是可以按顺序求。
我们可以考虑从小到大求,因为想要达到 \(mex = k\),我们必须要选择 所有 \(mex < k\) 的点,并且联通。我们就维护这个联通块,他是不断扩大的。
我们操纵 \(mex = 0\) 的点为根,这样可以保证联通块始终在树顶部。
可以用一个vis[]记录每个点是否在块里面。求解到 \(mex = i\ \ (1 <= i < n)\) 的点,设为x,
vis[x] == 1,说明这个点已经再块里。这表明不选x的情况下,无法选择 所有 \(i\) 的点,不满足题意,答案是-1。vis[x] == 0,直接往树根跳,并且把路上的点t都vis[t] = 1。并入联通块。答案就是n - sz[x],sz[x]为x子树(含自身)的大小。
特殊两种情况,\(mex = 0\) 的答案,是最大子树的大小。\(mex = n\) 的答案,是整棵树。
Code
时间复杂度 \(O(n)\)。一直寻址,常数不小。跑了 \(1124ms\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
const int maxn = 1e6 + 6;
vector<int> g[maxn];
int sz[maxn], f[maxn];
void dfs(int x, int fa) {
f[x] = fa;
sz[x] = 1;
for (auto &v : g[x]) {
if (v == fa) {
continue;
}
dfs(v, x);
sz[x] += sz[v];
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
a[x] = i;
}
for (int i = 2; i <= n; i++) {
int x;
cin >> x;
g[i].push_back(x);
g[x].push_back(i);
}
dfs(a[0], 0);
vector<int> vis(n + 1);
int cur = a[0];
vis[cur] = 1;
int res = 0;
// 第一个答案是 mex=0 节点的最大子树大小。
for (auto &v : g[cur]) {
res = max(res, sz[v]);
}
cout << max(res, n - sz[cur]) << ' ';
for (int i = 1; i < n; i++) {
int x = a[i];
if (vis[x]) {
cout << 0 << ' ';
} else {
int t = x;
while (!vis[t]) {
vis[t] = 1;
t = f[t];
}
cout << n - sz[x] << ' ';
}
}
cout << n << '\n';
return 0;
}
Bonus
赛时非常的糖,忘记把 \(mex = 0\) 的节点作为树根,导致联通块不一定在树顶。
写了个超级分类讨论,过了。代码:
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
const int maxn = 1e6 + 6;
vector<int> g[maxn];
int dep[maxn], sz[maxn], f[maxn];
void dfs(int x, int fa, int d) {
f[x] = fa;
dep[x] = d;
sz[x] = 1;
for (auto &v : g[x]) {
if (v == fa) {
continue;
}
dfs(v, x, d + 1);
sz[x] += sz[v];
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
a[x] = i;
}
for (int i = 2; i <= n; i++) {
int x;
cin >> x;
g[i].push_back(x);
g[x].push_back(i);
}
dfs(1, 0, 1);
vector<int> vis(n + 1);
int cur = a[0];
vis[cur] = 1;
int res = 0;
for (auto &v : g[cur]) {
if (v == f[cur]) {
continue;
}
res = max(res, sz[v]);
}
cout << max(res, n - sz[cur]) << ' ';
for (int i = 1; i < n; i++) {
if (vis[a[i]]) {
cout << 0 << ' ';
continue;
}
int t = a[i];
int ok1 = 0;
int ok2 = 0;
while (dep[cur] != dep[t]) {
if (dep[cur] > dep[t]) {
vis[cur] = 1;
cur = f[cur];
} else {
ok2 = 1;
vis[t] = 1;
t = f[t];
}
if (vis[t]) {
cout << n - sz[a[i]] << ' ';
ok1 = 1;
break;
}
}
vis[t] = 1;
if (ok1) {
continue;
}
if (!ok2 && cur == t) {
// cout << sz[a[i]] - 1 << ' ';
for (auto &v : g[cur]) {
if (v == f[cur]) {
continue;
}
// res = max(res, sz[v]);
if (vis[v]) {
res = sz[v];
}
}
cout << res << ' ';
continue;
}
while (cur != t) {
vis[cur] = vis[t] = 1;
t = f[t];
cur = f[cur];
}
vis[cur] = 1;
cout << n - sz[a[i]] << ' ';
}
cout << n << '\n';
return 0;
}

浙公网安备 33010602011771号