本家:Codeforces Round 964 (Div. 4)
A. A+B Again?
给定一个两位数,求两位数的数字和。
模拟。时间复杂度 O(1)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
int x;
cin >> x;
cout << x / 10 + x % 10 << '\n';
}
return 0;
}
B. Card Game
给两个人每人两张卡片,卡片上有个数字。初始时四张卡片都是面朝下的,一局游戏进行两个回合,每回合两个玩家各自翻开一张卡片,卡片上数字严格大于对方的,该回合获胜。每局游戏中获胜回合数最多的人,该局游戏获胜。现在求所有可能进行的对局中,玩家一能获胜的对局数量。
有点绕的题,代码直接暴力写了所有可能的情况,比较丑陋。
模拟。时间复杂度 O(1)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
int a, b, c, d;
cin >> a >> b >> c >> d;
int ans = (a > c and b >= d) or (a >= c and b > d);
ans += (a >= d and b > c) or (a > d and b >= c);
ans += (b >= c and a > d) or (b > c and a >= d);
ans += (b > d and a >= c) or (b >= d and a > c);
cout << ans << '\n';
}
return 0;
}
C. Showering
给定一个数轴 [0,m],和 n 段线段,线段霸占了数轴上的一段区间且线段之间互不重叠,求数轴上是否存在一段间隔至少为 s 的区间。
模拟。时间复杂度 O(n) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
int n, s, m;
cin >> n >> s >> m;
vector seg(n, pair(0, 0));
for (auto& [l, r] : seg) {
cin >> l >> r;
}
bool ok = seg.front().first >= s or m - seg.back().second >= s;
for (int i = 1; i < n; i++) {
ok |= seg[i].first - seg[i - 1].second >= s;
}
cout << (ok ? "YES\n" : "NO\n");
}
return 0;
}
D. Slavic's Exam
给定两个小写字符串 s 和 t , s 中某些位置的字符是 ? ,可以将其替换为任意小写字符。现在需要修改 s ,以满足 t 为 s 的子序列,输出构造方案或者判断不存在方案。
对 s 从前往后贪心匹配 t 的每一位即可, ? 字符直接修改为 t 当前正在和 s 匹配的字符。不太好文字描述,建议自己手搓几组字符串想一下。
贪心。时间复杂度 O(n) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
string s, t;
cin >> s >> t;
bool ok = false;
for (int i = 0, j = 0; i < s.size() and j < t.size(); i++) {
if (s[i] == t[j] or s[i] == '?') {
s[i] = t[j];
j++;
}
ok = j == t.size();
}
for_each(s.begin(), s.end(), [](auto& ch) { ch = ch == '?' ? 'a' : ch; });
if (ok) {
cout << "YES\n" << s << '\n';
} else {
cout << "NO\n";
}
}
return 0;
}
E. Triple Operations
给定一个区间 [l,r] ,和一种操作:每次在区间内随便选择两个不同的数 x 和 y ,将其中一个乘三,另外一个除三向下取整。给定 q 次询问,求将给定区间所有数变为 0 的最小操作次数。
容易发现,每个数字能除以三的次数是有限的。由于区间的值域在 2e5 以内,所以可以预处理每个数的除三次数,然后用前缀和优化一下,这样除三的次数就搞定了。那么乘三的次数呢?发现对于每个数字,除了几次三,就要找另外一个数字乘几次三。如果这个数字非零,那么最终我们还要对这个数字再次进行多余的操作,所以找 0 进行乘三操作即可,那么如何最快的构造出一个 0 呢?当然是直接对当前的区间的左端点除三了,其产生的乘三次数也是当前区间内所有数字中最小的。
数论、前缀和。时间复杂度 O(Dom+q∗log(l)) 。单次询问可以优化成 O(1) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
constexpr int maxn = 2e5;
vector pre(maxn + 1, 0ll);
pre[1] = 1, pre[2] = 2;
for (int i = 3, cur = 1; i <= maxn; i++) {
if (pow(3, cur) == i) {
cur++;
}
pre[i] = cur + pre[i - 1];
}
int tc;
cin >> tc;
while (tc--) {
int l, r;
cin >> l >> r;
int cnt = 0;
int t = l;
while (t > 0) {
t /= 3;
cnt++;
}
cout << pre[r] - pre[l - 1] + cnt << '\n';
// cout << pre[r] + pre[l] - pre[l - 1] * 2 << '\n'; O(1)写法
}
return 0;
}
F. Expected Median
给定一个长度为 n 的 01 数组 a ,对其中所有长度为 k (奇数)的子序列的中位数求和,输出求和后对 1e9+7 取模的结果。
中位数只有为 1 时才会对答案产生贡献,那么就枚举 1 的数量严格大于 0 的数量的所有方案,然后利用组合数公式对每种方案的数量进行计数即可。
组合数学。时间复杂度 O(n) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template <int mod>
struct mint {
int x = 0;
mint() {}
mint(ll x) : x(norm(x % mod)) {}
int norm(int x) {
x += x < 0 ? mod : 0;
x -= x >= mod ? mod : 0;
return x;
}
mint inv() { return (*this) ^ mod - 2; }
mint& operator+=(mint rhs) { return x = norm(x + rhs.x), *this; }
mint& operator-=(mint rhs) { return x = norm(x - rhs.x), *this; }
mint& operator*=(mint rhs) { return x = 1ll * x * rhs.x % mod, *this; }
mint& operator/=(mint rhs) { return *this *= rhs.inv(); }
mint& operator^=(ll exp) {
mint res(1);
for (mint& base = *this; exp > 0; base *= base, exp /= 2) {
if (exp & 1)
res *= base;
}
return *this = res;
}
friend mint operator+(mint lhs, mint rhs) { return lhs += rhs; }
friend mint operator-(mint lhs, mint rhs) { return lhs -= rhs; }
friend mint operator*(mint lhs, mint rhs) { return lhs *= rhs; }
friend mint operator/(mint lhs, mint rhs) { return lhs /= rhs; }
friend mint operator^(mint lhs, ll exp) { return lhs ^= exp; }
friend ostream& operator<<(ostream& os, mint rhs) { return os << rhs.x; }
friend istream& operator>>(istream& is, mint& rhs) {
ll x;
cin >> x;
rhs = x;
return is;
}
};
template <int mod>
struct Comb {
using Z = mint<mod>;
int n;
vector<Z> fac, inv;
Comb(int n) : n(n) {
fac.resize(n + 1, 1);
inv.resize(n + 1);
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i;
}
inv[n] = 1 / fac[n];
for (int i = n; i; i--) {
inv[i - 1] = inv[i] * i;
}
}
Z A(int n, int m) { return fac[n] * inv[n - m]; }
Z C(int n, int m) { return A(n, m) * inv[m]; }
Z D(int n) {
vector d(n + 1, Z(1));
for (int i = 1, sign = -1; i <= n; i++, sign = -sign) {
d[i] = i * d[i - 1] + sign;
}
return d[n];
}
Z C(int n) { return C(n * 2, n) - C(n * 2, n + 1); }
Z s(int n, int m) {
vector res(m + 1, Z(1));
for (int i = 1; i <= n; i++) {
for (int j = min(i, m); j > 0; j--) {
res[j] = res[j - 1] + (i - 1) * res[j];
}
res[0] = 0;
}
return res[m];
}
Z S(int n, int m) {
Z res = 0;
for (int i = 0, sign = 1; i <= m; i++, sign = -sign) {
res += sign * C(m, i) * (Z(m - i) ^ n);
}
return res * inv[m];
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
constexpr int mod = 1e9 + 7;
using Z = mint<mod>;
int tc;
cin >> tc;
while (tc--) {
int n, k;
cin >> n >> k;
int zero = 0, one = 0;
for (int i = 0, x; i < n; i++) {
cin >> x;
zero += x == 0;
one += x == 1;
}
Z ans;
Comb<mod> comb(n);
int mid = k / 2;
for (int i = 0; i <= mid; i++) {
if (zero < mid - i or one < mid + i + 1) {
continue;
}
ans += comb.C(zero, mid - i) * comb.C(one, mid + i + 1);
}
cout << ans << '\n';
}
return 0;
}
G1. Ruler (easy version)
交互题。
有一把尺子在 [2,999] 的范围内遗失了一个刻度 x ,对于测量的长度 len ,如果小于 x ,测量结果是 len ,如果大于等于 x ,测量结果是 len+1 。每次询问给定评测机一个矩形的长和宽,评测机返回的是对长和宽分别用尺子测量后的结果的乘积。在 10 次询问以内找到遗失的刻度。
发现刻度 1 没有遗失,且尺子满足二段性,那么就可以定死,长为 1 ,宽用二分法进行枚举,判断一下返回的乘积是否正常进行边界移动即可。 log2(999)<10 。
二分。时间复杂度 O(log(Dom)) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
auto ans = *ranges::partition_point(views::iota(2, 1000), [](int x) {
cout << format("? {} {}", 1, x) << endl;
int res;
cin >> res;
return res <= x;
});
cout << format("! {}", ans) << endl;
}
return 0;
}
G2. Ruler (hard version)
题意同 G1 ,但是询问次数上限为 7 。
发现 log3(999)<7 ,于是考虑三分,每次缩小 2/3 的区间。每次询问给定当前区间的两个三分点,并根据返回结果判断该取三个区间中的哪一个即可。
三分。时间复杂度 O(log(Dom)) 。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
cin >> tc;
while (tc--) {
int l = 2, r = 999;
while (l < r) {
int m0 = l + (r - l) / 3;
int m1 = r - (r - l) / 3;
cout << format("? {} {}", m0, m1) << endl;
int res;
cin >> res;
if (res == m0 * m1) {
l = m1 + 1;
} else if (res == m0 * (m1 + 1)) {
l = m0 + 1, r = m1;
} else {
r = m0;
}
}
cout << format("! {}", l) << endl;
}
return 0;
}