2024 CCPC 郑州邀请赛暨河南省赛 题解
A - Once In My Life (构造)
分析
根据题目要求需要两个d,并且要保证123456789都存在,那么就可以尝试先构造出一个数保证存在123456789。
尝试以下构造方式:设n的长度为len,则可以构造出\(k*n=({\frac{123456789*10^{len + 1}}n + 1}) * n\), 可以发现\(k*n\)的前缀一定是\(1234567890X\),此时只要对\(k\)每次+1,至多一百次一定能出现想要的\(d\)。
代码实现
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
#define int long long
constexpr int N = 123456789LL;
int tt;
for (std::cin >> tt; tt--;) {
int n, d;
std::cin >> n >> d;
int k = 123456789LL * std::pow<int>(10, 2 + (int)std::log10<int>(n)) / n + 1;
auto check = [&](int w) {
std::vector<int> c(10);
int num = w * n;
while (num) c[num % 10] += 1, num /= 10;
for (int i = 1; i <= 9; ++i) if (c[i] < 1) {
return false;
}
return c[d] >= 2;
};
while (!check(k)) k += 1;
std::cout << k << '\n';
}
}
B - 扫雷 1
分析
队友写的,并没有看题,就贴一下队友代码和思路:
贪心。要使得买的探测仪最多,就要尽可能的买价格较便宜的,相同便宜的要买后面出现的,因为这样手中的钱会更多。所以我们只需要把价格作为第一关键词升序排序,出现序号作为第二关键词降序排序,然后一个一个尽可能的多买,直到已经到最后一个时退出就行
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n;
cin >> n;
vector<tuple<ll , ll>> tl;
for (int i=1; i<=n; ++ i) {
int x;
cin >> x;
tl.emplace_back(x , -i);
}
sort(tl.begin() , tl.end());
ll ans = 0 , cur = 0 , pre = 0;
for (auto [a , b] : tl) {
b = -b;
if (b >= a && b > pre) {
ans += (b - cur) / a;
cur += (b - cur) / a * a;
pre = b;
}
if (b == n) break;
}
std::cout << ans << "\n";
}
C - 中二病也要打比赛 (树状数组+dp)
分析
依旧队友神力
代码实现
#include <bits/stdc++.h>
using namespace std;
template <typename T>
struct Fenwick {
int n;
vector<T> d;
Fenwick() : n(0) {}
Fenwick(int n) : n(n), d(n) {}
void modify(int x, T v) {
assert(0 <= x && x < n);
x += 1;
while (x <= n) {
d[x - 1] = max(d[x - 1], v);
x += x & -x;
}
}
T get(int x) {
assert(x <= n);
T v = 0;
while (x > 0) {
v = max(v, d[x - 1]);
x -= x & -x;
}
return v;
}
T get(int l, int r) {
assert(l <= r);
return get(r) - get(l);
}
int min_right(T k) {
int r = 0;
for (int e = __lg(n); ~e; --e) {
int u = (1 << e);
if (r + u <= n && d[r + u - 1] < k) {
k -= d[(r += u) - 1];
}
}
return r;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#define int long long
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
--a[i];
}
vector<int> l(n, n);
vector<int> r(n, 0);
for (int i = 0; i < n; ++i) {
l[a[i]] = min(l[a[i]], i);
r[a[i]] = max(r[a[i]], i);
}
for (int i = n - 1; i >= 0; ) {
assert(i == r[a[i]]);
int j = l[a[i]], k = i;
while (k >= j) {
r[a[k]] = i;
j = min(j, l[a[k]]);
--k;
}
i = k;
}
Fenwick<int> fen(n);
vector<int> dp(n);
for (int i = 0; i < n; ++i) {
assert(i == l[a[i]]);
for (int j = i; j <= r[a[i]]; ++j) {
if (j == l[a[j]]) {
dp[l[a[j]]] = fen.get(a[j]) + 1;
}
}
for (int j = i; j <= r[a[i]]; ++j) {
if (j == l[a[j]]) {
fen.modify(a[j], dp[l[a[j]]]);
}
}
i = r[a[i]];
}
cout << set<int>(a.begin(), a.end()).size() - *max_element(dp.begin(), dp.end()) << "\n";
}
D - 距离之比(推公式+贪心)
分析
设\(\bigtriangleup x = x_1 - x_2, \bigtriangleup y = y_1 - y_2\),对原公式进行平方并化简可得:\(1 + {\frac 1 {\frac {\bigtriangleup x} {\bigtriangleup y} + \frac {\bigtriangleup x} {\bigtriangleup y}}}\)。想让这个式子尽可能的大,也就是说让\({\frac {\bigtriangleup x} {\bigtriangleup y} + \frac {\bigtriangleup x} {\bigtriangleup y}}\)尽量取小。设\(tanθ = \frac {\bigtriangleup y} {\bigtriangleup x}\), 此时式子变化为\(tanθ + \frac 1 {tanθ}\), 可以发现其在\(θ\)为\(45^\circ\)和\(135^\circ\)时取到最优。因此只要对\(x + y\)和\(x - y\)分别进行排序,此时两两相邻为最优,两者取个最大值即可。
代码实现
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::cout << std::fixed << " " << std::setprecision(20);
#define int long long
int tt;
for (std::cin >> tt; tt--;) {
int n;
std::cin >> n;
std::vector<std::pair<int, int>> p(n);
for (auto &[x, y] : p) {
std::cin >> x >> y;
}
std::sort(p.begin(), p.end(),
[&](const auto& a, const auto& b) {
return a.first + a.second < b.first + b.second;
});
double ans = 0;
for (int i = 1; i < n; ++i) {
int d1 = p[i - 1].first - p[i].first, d2 = p[i - 1].second - p[i].second;
ans = std::max<double>(ans, ((abs(d1) + abs(d2)) * 1. / sqrt(d1 * d1 + d2 * d2)));
}
std::sort(p.begin(), p.end(),
[&](const auto& a, const auto& b) {
return a.first - a.second < b.first - b.second;
});
for (int i = 1; i < n; ++i) {
int d1 = p[i - 1].first - p[i].first, d2 = p[i - 1].second - p[i].second;
ans = std::max<double>(ans, ((abs(d1) + abs(d2)) * 1. / sqrt(d1 * d1 + d2 * d2)));
}
std::cout << ans << '\n';
}
}
E - 保卫城邦
分析
太菜了,不会写
F - 优秀字符串 (模拟)
分析
按照题意模拟即可
代码实现
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
int ans = 0;
for (int i = 0; i < n; ++i) {
std::string s;
std::cin >> s;
if (size(s) == 5 && s[2] == s[4] && std::set<char>{s[0], s[1], s[2], s[3]}.size() == 4) {
ans += 1;
}
}
std::cout << ans << '\n';
}
G - 扫雷 2 (构造)
分析
原来扫雷2的2是这个2щ(ಠ益ಠщ)
想了有两种构造方法:
第一种方法:
对于\(n \leq 2 * m\)时,我们可以直接按照如下方法构造
\(n = 5, m = 5\) \(n = 5, m = 6\)
01100 01100
11000 11000
10000 10000
00000 00000
00000 00001
此时对于m为奇数直接在左上角构造两个斜杠,为偶数时多出来的可以直接放在右下角。(注意特判\(n=5,m=10\)的情况)
此时对于\(n > 2 * m\)的情况我们考虑缩小矩阵,可将雷埋在最下面一行和最右边一行,此时可以得到\(n - 1\)的矩阵,例如 \(n = 6, m = 25\)时可以构造如下矩阵:
011011
110011
100011
000011
111111
111111
此时问题回到\(n \leq 2 * m\)的情况,注意构造过程中的细节即可。
第二种方法(感觉可行,但是并没有能写出来):
构造一个匚字型,形如:
\(n = 5, m = 7\) \(n = 5, m = 14\)
11100 11110
01000 10100
11100 10100
01000 10100
00000 11110
按照以上样子构造即可,构造完后多出来的雷往中间空出来的地方埋就行了,最后注意特判m较小的情况。(其实感觉可以方法一方法二结合写起来更简单一些)
代码实现(方法1)
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
auto bury = [&](std::vector<std::string>& adj, int num, int k) {
int len = (num + 1) / 2;
for (int i = 0; i < len; ++i) {
adj[i][len - i - 1]++;
}
for (int i = 0; i < len - 1; ++i) {
adj[i][len - i - 2]++;
}
adj[k][k] += num % 2 == 0;
};
int tt;
for (std::cin >> tt; tt--;) {
int n, m;
std::cin >> n >> m;
std::vector<std::string> ans(n, std::string(n, '0'));
if (m <= 2 * n) {
if (n == 5 && m == 10) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
ans[j][n - i - 1]++;
}
}
} else bury(ans, m, n - 1);
} else {
int now = n;
while (m >= 2 * now + 1 && now) {
m -= 2 * now - 1;
now--;
for (int i = 0; i < now; ++i) {
ans[i][now]++, ans[now][i]++;
}
ans[now][now]++;
}
if (m >= 2 && m < 2 * now - 1) {
ans[0][now - 1]++, ans[now - 1][0]++;
m -= 2;
}
if (m) {
if (now == 5 && m == 10) {
for (int i = 0; i < now; ++i) {
for (int j = 0; j < i; ++j) {
ans[j][now - i - 1]++;
}
}
} else if (now == 3 && m == 1) {
ans[now - 1][now - 1]++;
} else if (now == 4 && m == 8) {
bury(ans, m, now - 1);
ans[0][0]++, ans[now - 1][now - 1]--;
} else {
if ((now == 4 && (m == 3 || m == 4)) || (now == 3 && m == 2) || (now == 5 && m == 5)) {
ans[0][now - 1]--, ans[now - 1][0]--;
if (now == 4 && m == 4) {
ans[now - 1][now - 1]--;
ans[0][0]++;
}
m += 2;
}
if (now >= 5) {
if (now - (m + 1) / 2 == 2) {
ans[now - 2][now - 1]++, ans[now - 1][now - 2]++;
m -= 2;
}
if ((m & 1) && !(now == 5 && m == 9)) {
m += 1;
ans[n - 1][n - 1]--;
}
}
if (m) {
bury(ans, m, now - 1);
}
}
} else if (now == 3 || now == 2) {
ans[0][now - 1]--, ans[now - 1][0]--;
ans[1][1]++, ans[0 + 2 * (now == 3)][0 + 2 * (now == 3)]++;
}
}
std::cout << "Yes" << '\n';
for (int i = 0; i < n; ++i) {
std::cout << ans[i] << "\n";
}
}
}
H - 随机栈
分析
队友写的,题也没有看,贴一下队友代码和思路:
这题实际上是求这个随机栈的出栈序列是非降序的概率,然后求逆元就行。那么我们只需要求出这个概率的分子,分母,最后通过费马小定理用快速幂求出逆元就行
\(fenzi * ksm(fenmu , mod - 2)\)
求分子分母的过程就是利用古典概型分别求出总共的情况数(即分母),非降序的情况数(即分子)。
求分母的过程就是每次入栈 -1 时要乘上当前栈的大小,就能算出总共的情况数;
求分子的过程就是每次入栈 -1 时要乘上当前栈顶元素的个数,表示这些元素都能达到出栈序列非降序。
特别的当无法达成出栈序列是非降序时,概率为0,将分子置0就行
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll mod = 998244353;
//快速幂
constexpr int P = 998244353;
ll ksm(ll a , ll b) {
ll res = 1;
while (b) {
if (b & 1) res = (ll)res * a % P;
a = (ll)a * a % P;
b >>= 1;
}
return res;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
ll n , pren = 0 , fenzi = 1 , fenmu = 1;
cin >> n;
n *= 2;
priority_queue <ll, vector<ll>, greater<ll>> ql;
vector<ll> a(n+2);
map<ll , ll> mp;
for (int i=1; i <= n; ++ i) {
cin >> a[i];
if (a[i] == -1) {
ll len = (ll)ql.size();
fenmu = fenmu % mod * len % mod;
fenzi = fenzi % mod * mp[ql.top()] % mod;
pren = max(ql.top() , pren);
mp[ql.top()]--;
ql.pop();
} else if (a[i] != -1) {
ql.push(a[i]);
mp[a[i]] ++;
if (a[i] < pren) {
fenzi = 0;
}
}
}
ll ans = fenzi * ksm(fenmu , mod - 2) % mod;
std::cout << ans << "\n";
}
I - 378QAQ 和字符串 (枚举+hash+二分)
分析
枚举p,每次从p到n开始查找,每次跳过\(i\)和\(i-p\)的lcp,以i % p为起点,统计一下以此起点时需要修改的数量,并且标记一下i % p已经被修改过了,最后判断一下以p为循环节需要修改的数量是否小于k即可。(很极限的创过去了,队友还在想着如何用卷积卷过Test4 “v(〓 ̄(∵エ∵) ̄〓)v”)
代码实现
#include <bits/stdc++.h>
using ULL = unsigned long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int tt;
for (std::cin >> tt; tt--;)[&] {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
constexpr int P = 131;
std::vector<ULL> h(n + 1), p(n + 1);
p[0] = 1;
for (int i = 0; i < n; ++i) {
h[i + 1] = h[i] * P + s[i];
p[i + 1] = p[i] * P;
}
auto get = [&](int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
};
auto lcp = [&](int a, int b) {
int l = 0, r = n - b;
while (l < r) {
int mid = l + r >> 1;
if (get(a + 1, a + 1 + mid) == get(b + 1, b + 1 + mid)) {
l = mid + 1;
} else {
r = mid;
}
}
return r;
};
std::vector<int> vis(n);
for (int p = n / 4 + 1; p <= n / 2; ++p) {
int cost = 0;
for (int i = p; i < n && cost <= k; ++i) {
i += lcp(i - p, i);
if (i == n) break;
int x = i % p;
if (vis[x] != p) {
vis[x] = p;
std::vector<int> cnt(26);
int res = 0, tot = 0;
for (int j = x; j < n; j += p) {
tot += 1;
res = std::max(res, ++cnt[s[j] - 'a']);
}
cost += tot - res;
}
}
if (cost <= k) {
std::cout << "Yes" << '\n';
return ;
}
}
std::cout << "No" << '\n';
} ();
}
J - 排列与合数 (全排列)
分析
长度只有5,全排列暴力枚举即可,最后代码队友写的
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
//线性筛
constexpr int maxn = 1e6 + 10;
int pnl[maxn + 10], cnt;//pnl
int st[maxn + 10];//索引为质数值就是0
void init_primes() {
st[0]=1;
st[1]=1;
for (int i=2; i <= maxn; ++i) {
if (st[i] == 0){
pnl[cnt++] = i;
}
for (int j=0; pnl[j] <= maxn/i; ++j){
st[pnl[j]*i] = 1;
if (i%pnl[j] == 0) {
break;
}
}
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
init_primes();
int tt = 1;
cin >> tt;
while (tt--) [&]{
int n , res;
cin >> n;
vector<int> cur(5);
for (int j=4; j>=0; -- j) {
cur[j] = n % 10;
n /= 10;
}
sort(cur.begin(), cur.end());
do {
int nn = 0;
for (int j=0; j < 5; ++ j) {
nn = nn * 10 + cur[j];
}
if (1e4 <= nn && st[nn] == 1) {
std::cout << nn << '\n';
return ;
}
} while (next_permutation(cur.begin() , cur.end()));
std::cout << -1 << '\n';
}();
}
K - 树上问题 (并查集)
分析
可以将能够互为父子的视为一个集合,随后对集合之间连边,u->v代表u可以做v的父亲节点,最后找到唯一的根节点集合(唯一一个没有入度的集合,有多个则无解)即可。
代码实现
#include <bits/stdc++.h>
struct DSU {
std::vector<int> p, siz;
DSU(int n) : p(n), siz(n, 1) { std::iota(p.begin(), p.end(), 0); }
int leader(int x) {
while (x != p[x]) x = p[x] = p[p[x]];
return x;
}
bool same(int x, int y) { return leader(x) == leader(y); }
bool merge(int x, int y) {
x = leader(x), y = leader(y);
if (x == y) return false;
siz[x] += siz[y], p[y] = x;
return true;
}
int size(int x) { return siz[leader(x)]; }
};
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int tt;
for (std::cin >> tt; tt--;) {
int n;
std::cin >> n;
std::vector<int> val(n);
for (int i = 0; i < n; ++i) {
std::cin >> val[i];
}
DSU dsu(n);
std::vector<std::pair<int, int>> edges;
for (int i = 0; i < n - 1; ++i) {
int a, b;
std::cin >> a >> b;
a--, b--;
int num = (val[a] * 2 >= val[b]) + (val[b] * 2 >= val[a]);
if (num == 2) {
dsu.merge(a, b);
} else {
if (val[a] * 2 <= val[b]) {
edges.emplace_back(a, b);
} else {
edges.emplace_back(b, a);
}
}
}
std::vector<int> d(n);
std::vector<bool> use(n);
for (auto [x, y] : edges) {
x = dsu.leader(x), y = dsu.leader(y);
use[x] = use[y] = true;
d[y] += 1;
}
if (dsu.size(0) == n) {
std::cout << n << '\n';
} else {
int root = -1, num = 0;
for (int i = 0; i < n; ++i) {
if (d[i] == 0 && use[i]) {
root = i;
num += 1;
}
}
if (num == 1) {
std::cout << dsu.size(root) << '\n';
} else {
std::cout << 0 << '\n';
}
}
}
}
L - Toxel 与 PCPC II(dp)
分析
依旧队友神力,并没有读题的机会
代码实现
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#define int long long
int n, m;
cin >> n >> m;
vector<int> a(m);
for (int i = 0; i < m; ++i) {
cin >> a[i];
}
auto power = [&](int x) {
return x * x * x * x;
};
vector<int> dp(m + 1, 0);
for (int i = 1; i <= m; ++i) {
dp[i] = 2e18;
for (int j = max<int>(0, i - 400); j < i; ++j) {
dp[i] = min<int>(dp[i] , dp[j] + a[i - 1] + power(i - j));
}
}
cout << dp[m] << '\n';
}
M - 有效算法 (二分)
分析
可以发现\(k\)越大,\(x\)可取的范围越大,因此直接二分\(k\)的最小值,判断所有\(b_i * k\)可以取到的\(x\)是否有公共区间即可。
代码实现
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
#define int long long
int tt;
for (std::cin >> tt; tt--;) {
int n;
std::cin >> n;
std::vector<int> a(n), b(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++i) {
std::cin >> b[i];
}
auto check = [&](int k) {
int x = k * b[0];
int L = a[0] - x, R = a[0] + x;
for (int i = 1; i < n; ++i) {
x = k * b[i];
int l = a[i] - x, r = a[i] + x;
if (r < L || l > R) {
return false;
} else {
L = std::max(L, l);
R = std::min(R, r);
}
}
return true;
};
int l = 0 , r = 1e9;
while(l < r) {
int mid = l + r >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << r << '\n';
}
}