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)\)的数。那么\(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}, true);s.insert(v.max);}ans = std::max(ans, sum);}std::cout << ans << "\n";
}