VP Educational Codeforces Round 75 (Rated for Div. 2)
A. Broken Keyboard
题意:\(26\)个按键对\(26\)个字母,有些按键是坏的。好的按键按下后会出现一个字母,坏的会出现两个。现在给出一个输出序列。求哪些按键一定是好的。
好的按键需要满足,出现次数是奇数,或者有连续一段的长度是奇数。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
std::array<int, 26> st{}, cnt{};
for (int i = 0; i < s.size(); ++ i) {
int j = i;
while (j + 1 < s.size() && s[i] == s[j + 1]) {
++ j;
}
cnt[s[i] - 'a'] += j - i + 1;
st[s[i] - 'a'] |= j - i + 1 & 1;
i = j;
}
std::string ans;
for (int i = 0; i < 26; ++ i) {
if (cnt[i] % 2 || st[i]) {
ans += (char)('a' + i);
}
}
std::cout << ans << "\n";
}
B. Binary Palindromes
题意:给你\(n\)个\(01\)串,每次你可以交换任意两个串任意两个位置的数。求最多凑出多少个回文串。
先不讨论和其它串交换,看每个串能不能自己变成回文。记\(cnt0\)为\(0\)的个数,\(cnt1\)为\(1\)的个数。那么只要\(cnt0, cnt1\)不同为奇数,就可以变成回文。然后不能变成回文的可以两两操作变成回文。还有一个例外就是一个长度为奇数的可以变成回文的串,可以和不能变成回文的串操作使得它变成回文。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::string> s(n);
int cnt = 0, flag = 0, ans = 0;
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
int cnt0 = std::ranges::count(s[i], '0');
int cnt1 = std::ranges::count(s[i], '1');
if (cnt0 % 2 && cnt1 % 2) {
++ cnt;
} else {
++ ans;
flag |= cnt0 % 2 || cnt1 % 2;
}
}
ans += cnt / 2 * 2;
ans += flag && cnt % 2;
std::cout << ans << "\n";
}
C. Minimize The Integer
题意:给你一个数字串,如果相邻的两个数字奇偶性不同就可以交换。求最小数字串。
发现对于奇偶性相同的数字,它们的相对顺序是不变的。那么可以分奇偶存,每次拿最小的。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
std::string a, b;
for (auto & c : s) {
if (c - '0' & 1) {
a += c;
} else {
b += c;
}
}
std::string ans;
std::ranges::reverse(a);
std::ranges::reverse(b);
while (a.size() || b.size()) {
if (b.empty() || (a.size() && a.back() < b.back())) {
ans += a.back();
a.pop_back();
} else {
ans += b.back();
b.pop_back();
}
}
std::cout << ans << "\n";
}
D. Salary Changing
题意:有\(n\)个区间\([l_i, r_i]\),你要在每个区间选一个数然后减去它,你总共有\(s\)元。求最大的中位数。
考虑二分。
假设当前是\(x\),那么大于等于它的数字需要超过\(\lfloor \frac{n + 1}{2} \rfloor\),把\(r_i < x\)和\(l_i > x\)的给\(l_i\)。剩下的都是包含\(x\)的,我们按\(l_i\)从大到小选,如果大于等于\(x\)的数还不够就选\(x\),否则选\(l_i\)。最后只要总和小于等于\(s\)和选择个数大于等于\(\lfloor \frac{n + 1}{2} \rfloor\)就行。
点击查看代码
void solve() {
int n;
i64 s;
std::cin >> n >> s;
std::vector<int> l(n), r(n);
for (int i = 0; i < n; ++ i) {
std::cin >> l[i] >> r[i];
}
auto check = [&](int x) -> bool {
int cnt = 0;
i64 sum = 0;
std::vector<int> val;
for (int i = 0; i < n; ++ i) {
if (r[i] < x) {
sum += l[i];
} else {
if (l[i] > x) {
sum += l[i];
++ cnt;
} else {
val.push_back(l[i]);
}
}
}
std::ranges::sort(val, std::greater<>());
for (int i = 0; i < val.size(); ++ i) {
if (cnt < n / 2 + 1) {
++ cnt;
sum += x;
} else {
sum += val[i];
}
}
return sum <= s && cnt >= n / 2 + 1;
};
int lo = 1, hi = 1e9;
while (lo < hi) {
int mid = lo + hi + 1 >> 1;
if (check(mid)) {
lo = mid;
} else {
hi = mid - 1;
}
}
std::cout << lo << "\n";
}
E1 && E2. Voting
题意:有\(n\)个人,每个人你可以选择花费\(p_i\)让它加入你,或者已经有\(m_i\)个人以上加入你他就会自动加入你。求所有人加入你的最少花费。
考虑按\(m\)从大到小排序。
对于第\(i\)个人,假设后面的人都加入了,前面有一个集合\(P\)表示这里面的人没被氪金,但可能自动加入。那么如果\(n - |P| < m_i\),那么\(|P|\)里的人因为\(m\geq m_i\),则这些人都不可能自动加入了,那么必须从这些人里买人。显然应该从小到大买,可以用小根堆维护这个集合。
随着\(i\)的增大,如果一直不买人,则\(n - |P|\)一直变小,所以到后面总会购买。这时只要满足了\(m_i\),则其它比它大的也会满足。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::pair<int, int>> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i].first >> a[i].second;
}
std::ranges::sort(a, std::greater<>());
std::priority_queue<int, std::vector<int>, std::greater<int>> heap;
i64 ans = 0;
for (auto & [x, y] : a) {
heap.push(y);
while (n - (int)heap.size() < x) {
ans += heap.top();
heap.pop();
}
}
std::cout << ans << "\n";
}