比赛链接:https://codeforces.com/contest/1535
A. Fair Playoff
题解
可以考虑不成立的情况。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int a, b, c, d;
cin >> a >> b >> c >> d;
if (min(a, b) > max(c, d) or min(c, d) > max(a, b)) {
cout << "NO" << "
";
} else {
cout << "YES" << "
";
}
}
return 0;
}
B. Array Reodering
题解
为了尽可能地与 (2a_j) 配对,将因子中含有 (2) 的 (a_i) 排在前面。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end(), [](int x, int y) {
if (x % 2 != y % 2) {
return x % 2 == 0;
} else {
return x < y;
}
});
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (gcd(a[i], 2 * a[j]) > 1) {
++ans;
}
}
}
cout << ans << "
";
}
return 0;
}
C. Unstable String
题解
(dp_{i 0/1}) 表示第 (i) 位为 (0/1) 时,以第 (i) 位为终点的 (01) 间隔串的最大长度。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
long long ans = 0;
int n = s.size();
vector<vector<int>> dp(n, vector<int> (2));
for (int i = 0; i < n; i++) {
if (i == 0) {
if (s[i] == '?') {
dp[i][0] = dp[i][1] = 1;
} else if (s[i] == '0') {
dp[i][0] = 1;
} else {
dp[i][1] = 1;
}
} else {
if (s[i] == '?') {
dp[i][0] = dp[i - 1][1] + 1;
dp[i][1] = dp[i - 1][0] + 1;
} else if (s[i] == '0') {
dp[i][0] = dp[i - 1][1] + 1;
} else {
dp[i][1] = dp[i - 1][0] + 1;
}
}
ans += max(dp[i][0], dp[i][1]);
}
cout << ans << "
";
}
return 0;
}
D. Playoff Tournament
题解
由于整个比赛状态是一棵满二叉树,在满二叉树中,根节点序号为 (1) ,序号为 (i) 的结点左子结点序号为 (2i) ,右子结点序号为 (2i + 1) ,父结点为 (lfloor frac{i}{2} floor) ,所以可以用数组来实现。
接下来将叶子结点视为队伍,父结点视为比赛,按照如下方案更新每个结点:
-
初始时所有叶子结点的方案数赋为 (1) ,代表每支队伍
-
若某结点对应的比赛结果为 (0) ,则只继承左子结点的方案数
-
若某结点对应的比赛结果为 (1) ,则只继承右子结点的方案数
-
若某结点对应的比赛结果为 (?) ,则继承左子结点与右子结点的方案数之和
同理,在每次询问中,更新某次比赛结果后从其对应的结点往上修改即可。
Tips
为了方便在二叉树结点与比赛序号间进行转换,可以将结点 ([2^k-1,1]) 与比赛 ([0,2^k-2]) 依次对应,这样二者始终满足其和为 (2^k - 1) ,同时,为了满足左右子结点大小关系恒定的性质,队伍依次在底端从右往左编号,也因此需要交换比赛结果为 (0) 和 (1) 时的处理方案。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int k;
cin >> k;
string s;
cin >> s;
const int n = 1 << k;
vector<int> dp(2 * n);
for (int i = n; i < 2 * n; i++) {
dp[i] = 1;
}
for (int i = n - 1; i >= 1; i--) {
dp[i] = s[n - 1 - i] == '0' ? dp[2 * i + 1] : (s[n - 1 - i] == '1' ? dp[2 * i] : dp[2 * i + 1] + dp[2 * i]);
}
int q;
cin >> q;
while (q--) {
int p;
char c;
cin >> p >> c;
--p;
s[p] = c;
for (int i = n - 1 - p; i != 0; i /= 2) {
dp[i] = s[n - 1 - i] == '0' ? dp[2 * i + 1] : (s[n - 1 - i] == '1' ? dp[2 * i] : dp[2 * i + 1] + dp[2 * i]);
}
cout << dp[1] << "
";
}
return 0;
}
E. Gold Transfer
题解
由于父结点的花费小于子结点,所以始终选择路径上距离根结点最近且不为空的结点。
由于路径较长,考虑使用倍增,设 (p_{ij}) 是与结点 (i) 距离为 (2^j) 的结点,那么有状态转移方程: (p_{i j+1} = p_{p_{ij} j}) ,含义为 (2^{j + 1} = 2^j + 2^j) 。
之后依次贪心查找即可,每次查找的过程可以看作与最远非空结点间距离的二进制表示。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
constexpr int N = 20;
int q;
cin >> q;
vector<int> a(q + 1), c(q + 1), dep(q + 1);
vector<vector<int>> p(q + 1, vector<int> (N, -1));
cin >> a[0] >> c[0];
for (int i = 1; i <= q; i++) {
int op;
cin >> op;
if (op == 1) {
cin >> p[i][0];
dep[i] = dep[p[i][0]] + 1;
for (int j = 0; (1 << j) <= dep[i]; j++) {
p[i][j + 1] = p[p[i][j]][j];
}
cin >> a[i] >> c[i];
} else {
int v, w;
cin >> v >> w;
long long amount = 0, cost = 0;
while (w > 0 and a[v] > 0) {
int u = v;
for (int j = N - 1; j >= 0; j--) {
if (p[u][j] != -1 and a[p[u][j]] > 0) {
u = p[u][j];
}
}
int mi = min(w, a[u]);
w -= mi, a[u] -= mi;
amount += mi, cost += 1LL * mi * c[u];
}
cout << amount << ' ' << cost << endl;
}
}
return 0;
}