VP The 15th Shandong CCPC Provincial Collegiate Programming Contest
A. Project Management
题意:有\(n\)个人,第\(i\)个人有\(a_i, b_i\)两个属性,代表他可以接受最多\(b_i\)个\(a\)值大于\(a_i\)的人。求最多选多少人。
把相同\(a\)值的人存到一起,按\(b\)值从小到大排序。
那么可以从大到小枚举\(a\),记拿了\(k\)个比\(a\)值比\(a_i\)大的数。那么如果想拿\(x\)个\(a_i\)的数,拿\(b\)最大的前\(x\)个是最优的。依次枚举\(x\),可以得到需要删掉后面多少个,那么选择使人数增加最多的方案。因为前面的\(a\)都比当前选择的人的小,那么在前面的人眼里后面的人都是一样的,那么我们只需要保证人数最多就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<std::vector<std::pair<int, int>>> A(n + 1);
for (int i = 0; i < n; ++ i) {
int a, b;
std::cin >> a >> b;
A[a].emplace_back(b, i);
}
std::vector<int> ans;
for (int i = n; i >= 1; -- i) {
std::ranges::sort(A[i]);
int m = A[i].size();
int p = -1, max = 0;
for (int j = 0; j < m; ++ j) {
int sub = std::max(0, (int)ans.size() - A[i][j].first);
int add = m - j;
if (add - sub > max) {
max = add - sub;
p = j;
}
}
if (p != -1) {
int sub = std::max(0, (int)ans.size() - A[i][p].first);
while (sub -- ) {
ans.pop_back();
}
for (int j = p; j < m; ++ j) {
ans.push_back(A[i][j].second);
}
}
}
std::cout << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x + 1 << " \n"[x == ans.back()];
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. Bracket Integer
题意:给出一个数字,求小于等于这个数的最大合法括号数。一个括号数就是按数字分为不同的括号可以匹配的括号序列。
考虑枚举这个数和原始相同的前缀,然后枚举下一个数放什么,计算栈里剩下的元素数,这些后面肯定是一一对应的,然后中间放偶数个\(9\)。然后前缀越长这个数肯定越大,前缀相同的情况下肯定让下一个放更大的数。于是枚举出合法的最大的位置就行。然后模拟一下得到答案。没有合法的位置就输出偶数个\(9\)。长度小于\(n\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
int p = -1;
char ch = '0';
std::string stk;
stk.reserve(n);
for (int i = 0; i < n; ++ i) {
for (char c = s[i] - 1; c >= '0' + (i == 0); -- c) {
int x = stk.size();
if (x && c == stk.back()) {
-- x;
} else {
++ x;
}
int y = n - 1 - i;
if (y >= x && (y - x) % 2 == 0) {
p = i;
ch = c;
break;
}
}
if (stk.size() && s[i] == stk.back()) {
stk.pop_back();
} else {
stk.push_back(s[i]);
}
}
if (stk.empty()) {
std::cout << s << "\n";
return;
}
if (p == -1) {
std::string ans;
ans.reserve(n);
while (ans.size() + 2 < n) {
ans += "99";
}
std::cout << ans << "\n";
} else {
stk.clear();
std::string ans;
ans.reserve(n);
for (int i = 0; i < p; ++ i) {
ans += s[i];
if (stk.size() && s[i] == stk.back()) {
stk.pop_back();
} else {
stk.push_back(s[i]);
}
}
ans += ch;
if (stk.size() && ch == stk.back()) {
stk.pop_back();
} else {
stk.push_back(ch);
}
int cnt = (n - 1 - p - stk.size()) / 2;
for (int i = 0; i < cnt; ++ i) {
ans += "99";
}
while (stk.size()) {
ans += stk.back();
stk.pop_back();
}
std::cout << ans << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. Distributed System
题意:一个长度为\(n\)的数组,\(q\)个操作,每次给出\(a_i, b_i\),使得所有\(x \% n\)的位置加一,\(x \in [b_i, b_i + a_i]\)。
差分一下就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<i64> d(2 * n);
i64 sum = 0;
while (q -- ) {
int a, b;
std::cin >> a >> b;
sum += a / n;
a %= n;
d[b] += 1;
d[b + a] -= 1;
}
std::vector<i64> ans(n);
for (int i = 1; i + 2 < 2 * n; ++ i) {
d[i] += d[i - 1];
}
for (int i = 0; i + 2 < 2 * n; ++ i) {
ans[i % n] += d[i];
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] + sum << " \n"[i == n - 1];
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E. Greatest Common Divisor
题意:一个数组,恰好\(k\)次操作。每次把一个数加一。求最后数组的最大公约数。\(\sum max_{a_i} \leq 1e6\)。
设最后数组变为\(b\),最大公约数为\(x\),那么因为\(b_i\)都是\(x\)的倍数,那么\(sum_b\)也是\(x\)的倍数,也就是\(sum_a + k\)是\(x\)的倍数。
那么可以枚举这个\(x\)。则每个数先变为最近的\(x\)的倍数,然后剩下的操作数是\(x\)的倍数。
然后观察最大和的限制这个性质,考虑开个桶记录一下每个数的个数,然后预处理前缀和得到前\(i\)个数有多少个,以及它们的和。那么如果\(x \geq max_{a_i}\),可以直接计算至少需要加多少使得所有数是\(x\)的倍数。如果\(x \leq max_{a_i}\)。因为\([1, max_{a_i}]\)的每个数都只会算一次,那么可以用\(\lfloor \frac{max_{a_i}}{x} \rfloor\)的时间枚举每个\([x^{i-1} + 1, x^i - 1]\)的数需要多少操作。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
i64 k;
std::cin >> n >> k;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
i64 sum = std::accumulate(a.begin(), a.end(), 0ll);
i64 max = std::ranges::max(a);
std::vector<i64> cnt(max + max + 1), s(max + max + 1);
for (auto & x : a) {
++ cnt[x];
}
for (int i = 1; i <= 2 * max; ++ i) {
s[i] = cnt[i] * i + s[i - 1];
cnt[i] += cnt[i - 1];
}
i64 ans = 1;
auto get = [&](i64 x) -> void {
if (x <= ans) {
return;
}
if (x > max) {
i64 t = n * x - sum;
if (t <= k && (k - t) % x == 0) {
ans = x;
}
return;
}
i64 t = 0;
for (i64 i = 0, j = x; i <= max; i += x, j += x) {
t += (cnt[j - 1] - cnt[i]) * j - (s[j - 1] - s[i]);
if (t > k) {
return;
}
}
if ((k - t) % x) {
return;
}
ans = x;
};
for (i64 i = 1; i * i <= sum + k; ++ i) {
if ((sum + k) % i == 0) {
get(i);
if (i * i != (sum + k)) {
get((sum + k) / i);
}
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
G. Assembly Line
题意:一个长度为\(k\)的流水线,每个人位置一个时刻最多处理一个物品,把这个物品丢到下一个位置。有\(n\)个操作,每次在\(t_i\)时刻在\(w_i\)个位置加一个物品。求所有物品处理完需要的时间。
如果不考虑两个物品同时在一个地方的情况,则第\(i\)个物品的时间为\(a_i = t_i + k - w_i\)。
那么考虑从小到大处理,记\(b_i\)为处理前\(i\)个需要的时间,那么\(b_i = \max(b_{i-1} + 1, a_i)\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
int w, t;
std::cin >> w >> t;
a[i] = k - w + t;
}
std::ranges::sort(a);
for (int i = 1; i < n; ++ i) {
a[i] = std::max(a[i], a[i - 1] + 1);
}
std::cout << a.back() << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
H. Minimum Spanning Tree
题意:一个树,你还可以最多加\(k\)条边,边权为\(|u - v|\),求最小生成树。
优先考虑权值为\(0\)的边。
然后考虑加边,如果\(k \leq n -1\),那么显然可以加\((i, i + 1)\)这样的边,不算原图中权值为\(0\)的边的话这些边组成的最小生成树就是最小的。
那么反过来想,我们就加这\(n-1\)条边,多出来的考虑删去,那么可以把原图中前\(n - 1 - k\)小的边加进来。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct DSU {
std::vector<int> fa;
DSU(int n) {
fa.assign(n, 0);
std::ranges::iota(fa, 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
int u = find(x), v = find(y);
if (u == v) {
return false;
}
fa[v] = u;
return true;
}
};
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::tuple<int, int, int, int>> edges(m);
for (int i = 0; i < m; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
-- u, -- v;
edges[i] = {w, i, u, v};
}
std::ranges::sort(edges);
DSU dsu(n);
std::vector<int> ans;
i64 sum = 0;
int t = 0, cnt = std::max(0, n - 1 - k);
for (int i = 0; i < m && t < n - 1; ++ i) {
auto & [w, id, u, v] = edges[i];
if (w == 0) {
if (dsu.merge(u, v)) {
ans.push_back(id);
++ t;
}
continue;
}
if (t >= cnt) {
break;
}
if (dsu.merge(u, v)) {
ans.push_back(id);
sum += w;
++ t;
}
}
std::vector<std::pair<int, int>> add;
for (int i = 0, id = m; i + 1 < n && t < n - 1; ++ i) {
if (dsu.merge(i, i + 1)) {
add.emplace_back(i, i + 1);
ans.push_back(id);
id += 1;
sum += 1;
++ t;
}
}
std::cout << add.size() << "\n";
for (auto & [u, v] : add) {
std::cout << u + 1 << " " << v + 1 << "\n";
}
std::cout << sum << "\n";
for (auto & id : ans) {
std::cout << id + 1 << " \n"[id == ans.back()];
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
I. Square Puzzle
题意:一个长度为\(9\)的排列构成一个\(3 \times 3\)的矩阵。每从可以把一行右移,一列下移,以及整个矩阵顺时针旋转。给出两个矩阵,求第一个矩阵变成第二个矩阵的最小操作数。
考虑把数字看作标识,每个格子的编为为\(i \times 3 + j\)。那么每个矩阵其实都是按顺序从\(1\)到\(9\)的矩阵。然后每个数字有一个标识。则可以建立映射关系,也就是每个标识代表哪个格子。这样就可以把第一个矩阵和\(1\)到\(9\)这个矩阵关联起来,同时用这个映射关系变换第二个矩阵。那么问题变为从\(1\)到\(9\)这个矩阵变为\(b\)需要的最小步数。每个询问的起点就都是相同的,可以预处理。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
std::map<std::string, int> mp;
void init() {
std::string a = "123456789";
auto getr = [&](std::string s, int i) -> std::string {
char c = s[i * 3 + 2];
s[i * 3 + 2] = s[i * 3 + 1];
s[i * 3 + 1] = s[i * 3 + 0];
s[i * 3 + 0] = c;
return s;
};
auto getd = [&](std::string s, int j) -> std::string {
char c = s[6 + j];
s[6 + j] = s[3 + j];
s[3 + j] = s[j];
s[j] = c;
return s;
};
auto rotate = [&](std::string & s) -> std::string {
std::string t = s;
for (int i = 0; i < 3; ++ i) {
for (int j = 0; j < 3; ++ j) {
t[j * 3 + 3 - 1 - i] = s[i * 3 + j];
}
}
return t;
};
std::queue<std::string> q;
q.push(a);
mp[a] = 0;
while (q.size()) {
auto s = q.front(); q.pop();
for (int i = 0; i < 3; ++ i) {
auto t = getr(s, i);
if (!mp.count(t)) {
mp[t] = mp[s] + 1;
q.push(t);
}
}
for (int i = 0; i < 3; ++ i) {
auto t = getd(s, i);
if (!mp.count(t)) {
mp[t] = mp[s] + 1;
q.push(t);
}
}
auto t = rotate(s);
if (!mp.count(t)) {
mp[t] = mp[s] + 1;
q.push(t);
}
}
}
void solve() {
std::string a, b;
for (int i = 0; i < 3; ++ i) {
std::string t;
std::cin >> t;
a += t;
}
for (int i = 0; i < 3; ++ i) {
std::string t;
std::cin >> t;
b += t;
}
std::array<int, 10> to{};
for (int i = 0; i < 9; ++ i) {
to[a[i] - '0'] = i + 1;
}
for (int i = 0; i < 9; ++ i) {
b[i] = to[b[i] - '0'] + '0';
}
if (mp.count(b)) {
std::cout << mp[b] << "\n";
} else {
std::cout << -1 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
init();
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
L. Stella
签到题
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::map<char, int> mp;
std::string s = "OBAFGKM";
for (int i = 0; i < 7; ++ i) {
mp[s[i]] = 7 - i;
}
auto get = [&](std::string s) -> std::string {
std::string res;
res += mp[s[0]];
res += (9 - (s[1] - '0')) + '0';
return res;
};
std::string a, b;
std::cin >> a >> b;
a = get(a), b = get(b);
if (a > b) {
std::cout << "hotter\n";
} else if (a == b) {
std::cout << "same\n";
} else {
std::cout << "cooler\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}