Codeforces Round 1011 (Div. 2)
A. Serval and String Theory
题意:给你一个字符串\(s\),你每次可以交换其中两个位置的字符,最多操作\(k\)次,求可不可以使得\(s\)的字典序比\(res(s)\)小。其中\(rev(s)\)为\(s\)翻转后的的字符串。
首先如果\(s\)的所有位置都是一样的,无解。
否则如果\(s[n]\)都是所有字符里最小的,那么把\(s[n]\)换成最大的字符,否则\(s[1]\)是最小的,无需操作,都不是最小的,把\(s[1]\)换成最小的就行。
于是我们最多需要一次操作。注意如果\(s < rev(s)\)那么我们也不需要操作。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
std::string t = s;
std::reverse(t.begin(), t.end());
std::set<char> set;
for (auto & c : s) {
set.insert(c);
}
if (set.size() == 1 || (t <= s && k == 0)) {
std::cout << "NO\n";
} else {
std::cout << "YES\n";
}
}
B. Serval and Final MEX
题意:给你一个数组,每次选一个区间,把这个区间的都删掉,然后插入它们的\(mex\)。要让最后只剩下一个\(0\)。求操作方案。
分类讨论。
首先找到最左边和最右边的\(0\),记为\(l, r\)。
如果没有\(0\),那么直接一次操作\([1, n]\)就行。
否则如果\(l=r\),如果\(l=1\)则需要先操作\([l, n - 1]\),然后剩下就没有\(0\)了。否则如果\(l=n\),先操作\([2, n]\),剩下就没有\(0\)了。
如果\(l!=r\)也是类似上面的讨论,特判有没有\(0\)在边界的情况。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int l = 0, r = n - 1;
while (l < r && a[l] != 0) {
++ l;
}
while (l < r && a[r] != 0) {
-- r;
}
if (a[l] != 0) {
std::cout << 1 << "\n";
std::cout << 1 << " " << n << "\n";
} else if (l == r) {
std::cout << 2 << "\n";
if (l == 0) {
std::cout << 1 << " " << n - 1 << "\n";
std::cout << 1 << " " << 2 << "\n";
} else if (l == n - 1) {
std::cout << 2 << " " << n << "\n";
std::cout << 1 << " " << 2 << "\n";
} else {
std::cout << 2 << " " << n << "\n";
std::cout << 1 << " " << 2 << "\n";
}
} else {
if (l == 0 && r == n - 1) {
std::cout << 3 << "\n";
std::cout << 3 << " " << n << "\n";
std::cout << 1 << " " << 2 << "\n";
std::cout << 1 << " " << 2 << "\n";
} else if (l == 0) {
std::cout << 2 << "\n";
std::cout << 1 << " " << n - 1 << "\n";
std::cout << 1 << " " << 2 << "\n";
} else if (r == n - 1) {
std::cout << 2 << "\n";
std::cout << 2 << " " << n << "\n";
std::cout << 1 << " " << 2 << "\n";
} else {
std::cout << 3 << "\n";
std::cout << r + 1 << " " << n << "\n";
std::cout << 1 << " " << r << "\n";
std::cout << 1 << " " << 2 << "\n";
}
}
}
C. Serval and The Formula
题意:给你\(x, y\),找一个\(k\),使得\((x + k) + (y + k) = (x + k) \oplus (y + k)\)。
首先\(a + b \geq a \oplus b\),大于的情况是\(a+b\)产生了进位。等于的情况则是没有进位,如果没有进位,那么\(a, b\)每一位都不同时为\(1\)。
那么显然\(x=y\)是无解的,那么如果\(x\ne y\)我们可以求一个\(s\)使得\(s\)是第一个大于等于\(\max(x, y)\)的\(2\)的幂的数。那么\(k\)可以等于\(s - \max(x, y)\)。假设\(x > y\),那么\(x + k\)后变成了\(s\),\(y + k<s\),而\(s\)除了最高位其它位都是\(0\),所以符合条件。
点击查看代码
void solve() {
i64 x, y;
std::cin >> x >> y;
if (x == y) {
std::cout << -1 << "\n";
return;
}
i64 n = std::max(x, y);
i64 s = 1;
while (s < n) {
s <<= 1;
}
i64 k = s - n;
std::cout << k << "\n";
}
D. Serval and Kaitenzushi Buffet
题意:有\(n\)个物品,每个物品有一个价值,你按顺序操作,每次选择拿走一个物品,或者消化之前拿过的某个物品,或者上面都不做。每个物品需要\(k\)次才能消化,最后你要恰好消耗拿过的所有物品,求最大价值。
如果我们选择了\(m\)个物品,那么我们总共需要\(m \times (k + 1)\)次操作。但并不是操作数满足就合法,因为我们需要每个后缀都能满足,也就是说,只拿最后一个物品可以满足条件,只拿最后两个物品可以满足条件... 那么我们可以从后往前枚举,最后一个我们只能在\([1, n - k]\)里拿,最后两个我们只可以在\([1, n - 2k - 1]\)里拿... 那么我们把数组分成\(\lfloor \frac{n}{k+1} \rfloor\)段,从后往前找每一段的最右边可以拿到的位置\(i\),那么这一段可以在\([i - k, i]\)拿一个数。我们还需要考虑用当前区间的某个数换之前的数,因为我们用更前面的数换后面的数肯定也合法。那么算法如下:
我们用线段树维护区间最大值。如何从后往前枚举,用一个\(multiset\)存已经拿过的数,每次操作一个新区间,如果这个区间的最大值比之前拿到过的数最小值大,我们就不断更换,每次更换后把最大值的位置修改成\(0\)。
总共有\(\lfloor \frac{n}{k+1} \rfloor\)个区间,每个区间最多操作\(k + 1\)次,那么时间复杂度就是线段树的复杂度,是\(nlogn\)。
点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)
template <class Info>
struct Node {
int l, r;
Info info;
};
template <class Info>
struct SegmentTree {
std::vector<Node<Info> > tr;
SegmentTree(int _n) {
init(_n);
}
SegmentTree(std::vector<Info> & a) {
init(a);
}
void init(int _n) {
tr.assign(_n << 2, {});
build(0, _n - 1);
}
void init(std::vector<Info> & a) {
int _n = (int)a.size();
tr.assign(_n << 2, {});
build(0, _n - 1, a);
}
void pushup(int u) {
tr[u].info = tr[ls].info + tr[rs].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
return;
}
int mid = l + r >> 1;
build(l, mid, ls); build(mid + 1, r, rs);
}
void build(int l, int r, std::vector<Info> & a, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info = a[l];
return;
}
int mid = l + r >> 1;
build(l, mid, a, ls); build(mid + 1, r, a, rs);
pushup(u);
}
void modify(int p, Info add, int u = 1) {
if (tr[u].l == tr[u].r) {
tr[u].info = add;
return;
}
int mid = umid;
if (p <= mid) {
modify(p, add, ls);
} else {
modify(p, add, rs);
}
pushup(u);
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
int mid = umid;
if (r <= mid) {
return query(l, r, ls);
} else if (l > mid) {
return query(l, r, rs);
}
return query(l, r, ls) + query(l, r, rs);
}
};
struct Info {
i64 max, p;
};
Info operator + (const Info & a, const Info & b) {
Info res{};
if (a.max >= b.max) {
res = a;
} else {
res = b;
}
return res;
}
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<i64> a(n);
std::vector<Info> info(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
info[i] = {a[i], i};
}
SegmentTree<Info> tr(info);
i64 ans = 0, sum = 0;
std::multiset<i64> s;
for (int i = n - 1 - k; i >= 0; i -= k + 1) {
ans = std::max(ans, sum + tr.query(0, i).max);
int l = std::max(0, i - k);
s.insert(0);
while (tr.query(l, i).max > *s.begin()) {
sum -= *s.begin();
s.erase(s.begin());
auto v = tr.query(l, i);
sum += v.max;
tr.modify(v.p, Info{0, v.p});
s.insert(v.max);
}
ans = std::max(ans, sum);
}
std::cout << ans << "\n";
}
E. Serval and Modulo
题意:给你两个数组\(a, b\),求是否存在一个\(k, k \in [1, 10^9]\),使得\(b\)重排后与\(b_i = a_i \% k\)。
赛后补题
这题其实挺简单的。如果放\(c\)应该能过很多人。
只要发现对应的\(b_i\)比\(a_i\)少的值是\(k\)的倍数这题基本上就做完了。
那么根据上面的结论,\(sum_a - sum_b\)应该也是\(k\)的倍数,那么我们暴力枚举\(sum_a - sum_b\)的因子判断就行了。
点击查看代码
void solve() {
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];
}
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
i64 suma = std::accumulate(a.begin(), a.end(), 0ll);
i64 sumb = std::accumulate(b.begin(), b.end(), 0ll);
if (suma - sumb < 0) {
std::cout << -1 << "\n";
return;
} else if (suma == sumb) {
if (a == b) {
int ans = 1e9;
std::cout << ans << "\n";
} else {
std::cout << -1 << "\n";
}
return;
}
auto check = [&](i64 k) -> bool {
if (k > 1e9) {
return false;
}
auto t = a;
for (auto & x : t) {
x %= k;
}
std::sort(t.begin(), t.end());
return t == b;
};
i64 d = suma - sumb;
for (i64 i = 1; i * i <= d; ++ i) {
if (d % i == 0) {
if (check(i)) {
std::cout << i << "\n";
return;
}
if (check(d / i)) {
std::cout << d / i << "\n";
return;
}
}
}
std::cout << -1 << "\n";
}