Educational Codeforces Round 183 (Rated for Div. 2)
A. Candies for Nephews
题意:求大于等于\(n\)的最小\(3\)的倍数。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::cout << (n + 2) / 3 * 3 - n << "\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;
}
B. Deck of Cards
题意:一副牌有\(n\)张,每次从顶拿一张或者从底拿一张,但有些操作你不确定是从哪拿的。求每张卡是一定被拿了还是一定没被拿还是不确定。
记录每个操作的数量,那么对于第\(i\)张卡,如果顶上或底下确定可以拿它,则必被拿。想要不被拿,需要顶上和底下各自加上不确定的数量都拿不到它。否则如果操作数小于\(n\)就是可能被拿。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
int cnt[3]{};
for (auto & c : s) {
++ cnt[c - '0'];
}
std::string ans;
for (int i = 1; i <= n; ++ i) {
if (cnt[0] < i && cnt[1] <= n - i) {
if (cnt[0] + cnt[2] < i && cnt[1] + cnt[2] <= n - i) {
ans += '+';
} else {
if (cnt[0] + cnt[1] + cnt[2] == n) {
ans += '-';
} else {
ans += '?';
}
}
} else {
ans += '-';
}
}
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;
}
C. Monocarp's String
题意:一个只包含\(ab\)的字符串,删去一个子区间使得\(ab\)数量相等,求区间最小长度。
记\(a\)为\(-1\),\(b\)为\(1\)。那么我们需要删掉一个子区间满足其区间和为\(b\)的数量减\(a\)的数量。
可以前缀和做,用\(map\)存前面每个前缀值出现的最晚下标。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<int> sum(n + 1);
for (int i = 0; i < n; ++ i) {
sum[i + 1] = sum[i] + (s[i] == 'a' ? -1 : 1);
}
if (sum[n] == 0) {
std::cout << 0 << "\n";
return;
}
int ans = n;
std::map<int, int> mp;
mp[0] = 0;
for (int i = 1; i <= n; ++ i) {
if (mp.count(sum[i] - sum[n])) {
ans = std::min(ans, i - mp[sum[i] - sum[n]]);
}
mp[sum[i]] = i;
}
if (ans == n) {
ans = -1;
}
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. Inversion Value of a Permutation
题意:构造一个长度为\(n\)的排列,使得其恰好有\(k\)个子数组不是升序。\(n \leq 30\)。
先考虑倒着排:\(n, n - 1, n - 2, ... 1\),这样有\(\frac{n(n-1)}{2}\)个不是升序的区间。然后我们选择一个长度为\(l\)的子区间翻转的话,就使得满足条件的区间减少了\(\frac{l(l-1)}{2}\)。然后我们选择一些不相交的子区间翻转,这样我们可以凑出减少\(\frac{n(n-1)}{2} - k\)。但怎样凑也是有最优方案的,可以看作背包,重量为\(l\)的物品有\(\frac{l(l-1)}{2}\)的价值。求凑\(\frac{n(n-1)}{2} - k\)价值的最小重量及其方案。
背包预处理即可。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 30 * 30;
int f[N], pre[N];
void init() {
for (int i = 1; i < N; ++ i) {
f[i] = 1e9;
for (int j = 2; j * (j - 1) / 2 <= i; ++ j) {
if (f[i - j * (j - 1) / 2] + j < f[i]) {
f[i] = f[i - j * (j - 1) / 2] + j;
pre[i] = j;
}
}
}
}
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> ans(n);
std::ranges::iota(ans, 0);
std::ranges::reverse(ans);
k = n * (n - 1) / 2 - k;
for (int i = 0; i < n && k;) {
int len = pre[k];
if (i + len > n) {
break;
}
std::reverse(ans.begin() + i, ans.begin() + i + len);
k -= len * (len - 1) / 2;
i += len;
}
if (k) {
std::cout << 0 << "\n";
} else {
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] + 1 << " \n"[i == n - 1];
}
}
}
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;
}
E. Predicting Popularity
题意:一开始你有两个属性\(ac, dr\)。有\(n\)个人也有两个属性\(a_i, d_i\)。你可以一步一步吸收这些人,如果已经有\(p\)个人,那么如果一个人满足\(\max(0, a_i - ac) + \max(0, d_i - dr) \leq p\)他就会加入你。\(m\)次修改每次改一个人的\(a_i, d_i\),求可以有多少人。
用\(cnt_k\)存\(k = \max(0, a_i - ac) + \max(0, d_i - dr)\)的个数。记\(pre_i = \sum_{j=0}^{i-1} cnt_j\)。那么如果有\(pre_i \geq i\)则可以加入\(i\)个人。那么最大的所有\(pre_i\)都大于等于\(i\)的前缀长度就是答案。
现在考虑修改,记\(f_i = pre_i - i\)。那么最大的\(f_i\)大于等于\(0\)的前缀长度就是答案,可以用线段树维护\(f\)。修改一个人时先把他原来的贡献删掉,如果它的\(\max(0, a_i - ac) + \max(0, d_i - dr) = k\),那么\([k + 1, n + 1]\)的\(f\)都要减一,\(k\)大于\(n\)就看作\(n+1\)。
然后修改后同理把修改后对应的后缀都加一就行了。然后查询答案就是在线段树内二分。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 5e5 + 5;
int a[N], d[N];
int f[N];
template <class Info, class Tag>
struct LazySegmentTree {
struct Node {
int l, r;
Info info;
Tag tag;
};
std::vector<Node> tr;
LazySegmentTree() {}
LazySegmentTree(int n) {
init(n);
}
void init(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushdown(Node & u, const Tag & tag) {
u.info = u.info + tag;
u.tag = u.tag + tag;
}
void pushdown(int u) {
if (tr[u].tag.exist()) {
pushdown(tr[u << 1], tr[u].tag);
pushdown(tr[u << 1 | 1], tr[u].tag);
tr[u].tag.clear();
}
}
void pushup(int u) {
tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info.min = f[l];
return;
}
int mid = l + r >> 1;
build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
pushup(u);
}
void modify(int l, int r, const Tag & tag, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
pushdown(tr[u], tag);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) {
modify(l, r, tag, u << 1);
}
if (r > mid) {
modify(l, r, tag, u << 1 | 1);
}
pushup(u);
}
int query(int u = 1) {
if (tr[u].l == tr[u].r) {
return tr[u].info.min >= 0 ? tr[u].l : 0;
}
pushdown(u);
if (tr[u << 1].info.min >= 0) {
int mid = tr[u].l + tr[u].r >> 1;
return std::max(mid, query(u << 1 | 1));
} else {
return query(u << 1);
}
}
};
struct Info {
int min;
};
struct Tag {
int add = 0;
bool exist() {
return add != 0;
}
void clear() {
add = 0;
}
};
Info operator + (const Info & a, const Info & b) {
Info res{};
res.min = std::min(a.min, b.min);
return res;
}
Info operator + (const Info & a, const Tag & b) {
Info res{};
res.min = a.min + b.add;
return res;
}
Tag operator + (const Tag & a, const Tag & b) {
Tag res{};
res.add = a.add + b.add;
return res;
}
void solve() {
int ac, dr;
std::cin >> ac >> dr;
int n;
std::cin >> n;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++ i) {
std::cin >> d[i];
}
std::vector<int> cnt(n + 3);
for (int i = 0; i < n; ++ i) {
int v = std::max(0, a[i] - ac) + std::max(0, d[i] - dr);
++ cnt[std::min(n + 1, v)];
}
int pre = 0;
for (int i = 0; i <= n + 2; ++ i) {
f[i] = pre - i;
pre += cnt[i];
}
LazySegmentTree<Info, Tag> tr(n + 3);
int m;
std::cin >> m;
while (m -- ) {
int k, x, y;
std::cin >> k >> x >> y;
-- k;
int last = std::max(0, a[k] - ac) + std::max(0, d[k] - dr);
last = std::min(n + 1, last);
tr.modify(last + 1, n + 2, Tag{-1});
a[k] = x; d[k] = y;
int now = std::max(0, a[k] - ac) + std::max(0, d[k] - dr);
now = std::min(n + 1, now);
tr.modify(now + 1, n + 2, Tag{1});
std::cout << tr.query() << "\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;
}
F. Long Journey
题意:一个人从\(0\)走到\(m\)每步可以选择不动或者加一。有\(n\)个陷阱,第\(i\)个陷阱会在时刻\(t\)满足\(t \equiv i \pmod n\)时在所有\(k \times a_i + b_i\)的位置触发。不能碰到陷阱走到\(m\)需要多少步。\(n \leq 10, m \leq 10^{12}\)。
记状态\(f[i][j]\)表示走到第\(i\)个格子时刻为\(j\)的最小步数,转移就是每次走下一个最小时刻\(k\)满足\(j\)等到\(k\)第\(i\)个格子都是安全的,且下一步走到\(i+1\)也是安全的。显然太大了无法计算。考虑状态取下模。即\(f[i][j]\)表示第一次到\(x \equiv i \pmod {lcm(a_1, a_2, ..., a_n)}\)格,时刻为\(t \equiv j \pmod n\)的最小步数。然后猜测在这些转移里是有环的(真是猜的)。只需要找到这个环,就能计算了。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve(){
int n;
i64 m;
std::cin >> n >> m;
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];
}
int L = 1;
for(int i = 0; i < n; i++) {
L = std::lcm(L, a[i]);
}
int N = L * n;
std::vector<std::vector<int>> safe(L, std::vector<int>(n, 1));
for(int x = 0; x < L; ++ x) {
for(int t = 0; t < n; ++ t) {
int i = (t - 1 + n) % n;
if (x % a[i] == b[i]) {
safe[x][t] = 0;
}
}
}
std::vector<int> f(N, -1);
std::vector<i64> cost(N, 0);
auto getNext = [&](int id) -> void {
if (f[id] != -1) {
return;
}
int x = id / n;
int r = id % n;
int best = -1;
for(int w = 0; w < n; ++ w){
bool ok = true;
for(int k = 1; k <= w; ++ k){
int t2 = (r + k) % n;
if (!safe[x][t2]) {
ok = false; break;
}
}
if (!ok) {
continue;
}
int rt = (r + w + 1) % n;
int x2 = (x + 1 == L ? 0 : x + 1);
if (safe[x2][rt]) {
best = w;
break;
}
}
if (best < 0) {
f[id] = -2;
return;
}
i64 c = best + 1;
int r2 = (r + best + 1) % n;
int x2 = (x + 1 == L ? 0 : x + 1);
int id2 = x2 * n + r2;
f[id] = id2;
cost[id] = c;
};
int id = 0;
std::vector<i64> sum(N, 0);
std::vector<i64> d(N, -1);
i64 tot = 0;
i64 step = 0;
int cur = id;
bool bad = false;
while (step < m && d[cur] == -1) {
d[cur] = step;
sum[cur] = tot;
getNext(cur);
if (f[cur] == -2) {
bad = true;
break;
}
tot += cost[cur];
cur = f[cur];
step ++;
}
if (bad) {
std::cout << -1 << "\n";
return;
}
if (step == m) {
std::cout << tot << "\n";
return;
}
i64 cycle_len = step - d[cur];
i64 cycle_cost = tot - sum[cur];
i64 rem = m - d[cur];
i64 cnt = rem / cycle_len;
i64 ans = sum[cur] + cnt * cycle_cost;
i64 rem2 = rem % cycle_len;
for (i64 i = 0; i < rem2; ++ i) {
getNext(cur);
ans += cost[cur];
cur = f[cur];
}
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;
}