2025-9-29 总结(ZJCPC 2017 重现赛)
A - Cooking Competition
情况
- 时间:\(10min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 贪心
思路
我们可以发现 \(3\) 和 \(4\) 的操作对于两人分数的差没有任何影响,所以我们只需要统计操作 \(1\) 和 \(2\) 的数量即可,如果 \(1\) 比 \(2\) 数量多则输出 Kobayashi,\(1\) 的数量比 \(2\) 少则输出 Tohru,相等则输出 Draw。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t, n, cnt1, cnt2;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; cnt1 = cnt2 = 0) {
cin >> n;
for (int i = 1, x; i <= n; i++) {
cin >> x, cnt1 += x == 1, cnt2 += x == 2;
}
cout << (cnt1 < cnt2 ? "Tohru\n" : (cnt1 > cnt2 ? "Kobayashi\n" : "Draw\n"));
}
return 0;
}
B - Problem Preparation
情况
- 时间:\(10min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 贪心
思路
我们需要判断排序过的数组 \(a\) 以下条件是否全部满足,如果都成立则输出 Yes 否则输出 No:
- \(10\le n\le13\);
- 第一个数大于等于 \(1\);
- 第二个数小于等于 \(1\);
- \(a_i-a_{i-1}\le 2(2\le i\le n-1)\)。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 110;
int a[kMaxN], t, n;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--;) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
if (a[1] < 1 || a[2] > 1 || n < 10 || n > 13) {
cout << "No\n";
} else {
int flag = 1;
for (int i = 2; i < n; i++) {
flag &= a[i] - a[i - 1] <= 2;
}
cout << (flag ? "Yes\n" : "No\n");
}
}
return 0;
}
C - What Kind of Friends Are You?
情况
- 时间:\(30min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 状压
思路
由于 \(q\) 十分小,所以我们考虑对于每一个名称,如果第 \(j\) 个字符串,在问题 \(i(0\le i<q)\) 中出现过,那么给 \(j\) 加上 \(2^i\),所以最后查询就只需要 \(O(n)\) 了。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 210;
int t, n, m, x;
string s[kMaxN], c;
map<string, int> mp;
map<int, vector<string> > mp2;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; mp.clear(), mp2.clear()) {
cin >> n >> m >> x;
for (int i = 1; i <= x; i++) {
cin >> s[i];
}
for (int i = 0, k; i < m; i++) {
for (cin >> k; k--;) {
cin >> c, mp[c] += 1ll << i;
}
}
for (int i = 1; i <= x; i++) {
mp2[mp[s[i]]].push_back(s[i]);
}
for (int i = 1; i <= n; i++) {
int sum = 0;
for (int j = 0, k; j < m; j++) {
cin >> k, sum += k * (1ll << j);
}
if (!mp2[sum].size() || mp2[sum].size() > 1) {
cout << "Let's go to the library!!\n";
} else {
cout << mp2[sum][0] << '\n';
}
}
}
return 0;
}
D - Let's Chat
情况
- 时间:\(20min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 暴力
思路
我们可以枚举 \(x\) 和 \(y\) 中的两段,看他们的交集,我们可以证明,不可能存在四个段 \(a,b,c,d(a,b\in x,c,d\in y,a\neq b,c\neq d)\),使得他们的交集的交集大于等于 \(1\),所以我们统计出每一个交集(假设长度为 \(len\)),直接把答案加上 \(\max(0,len-m+1)\) 即可。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 210;
int t, n, m, x, y, ans;
pair<int, int> p[2][kMaxN];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; ans = 0) {
cin >> n >> m >> x >> y;
for (int i = 1; i <= x; i++) {
cin >> p[0][i].first >> p[0][i].second;
}
for (int i = 1; i <= y; i++) {
cin >> p[1][i].first >> p[1][i].second;
}
for (int i = 1; i <= x; i++) {
for (int j = 1; j <= y; j++) {
int l = max(p[0][i].first, p[1][j].first), r = min(p[0][i].second, p[1][j].second);
ans += max(0ll, r - l - m + 2);
}
}
cout << ans << '\n';
}
return 0;
}
E - Seven Segment Display
情况
- 时间:\(20min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- \(\text{dp}\)
思路
我们发现其实答案只有两种情况 \(n+s\ge16^8\) 要拆成两个部分,\(n+s<16^8\) 直接计算一个部分的答案即可,我们假设 \(S(x)\) 函数是用来计算 \(0~x\) 的和的,所以答案有两种:
- 答案为 \(S(FFFFFFFF)-S(s-1)+S(n+s-16^8)\);
- 答案为 \(S(n+s)-S(s-1)\)。
所以我们现在思考如何计算 \(S\),但只要有脑袋的人肯定会想到数位 \(\text{dp}\),然后就解决了。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 110, fx[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6, 6, 5, 4, 5, 5, 4};
int b[kMaxN], dp[kMaxN][kMaxN], t, n, m, fac;
string s;
int D(int x, int sum, int flag) {
if (x < 0) {
return sum;
}
if (!flag && dp[x][sum] != -1) {
return dp[x][sum];
}
int cnt = 0;
for (int i = 0; i <= (flag ? b[x] : 15); i++) {
cnt += D(x - 1, sum + fx[i], flag && i == (flag ? b[x] : 15));
}
return flag ? cnt : dp[x][sum] = cnt;
}
int S(int x) {
if (x < 0) {
return 0;
}
int pos = 0;
for (memset(b, 0, sizeof b); x > 0; b[pos++] = x % 16, x /= 16);
return D(7, 0, 1);
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (int i = 0; i < 8; i++) {
fac = fac * 16 + 15;
}
for (cin >> t, memset(dp, -1, sizeof dp); t--; m = 0) {
cin >> n >> s;
for (int i = 0; i < 8; i++) {
m = m * 16 + ('0' <= s[i] && s[i] <= '9' ? s[i] - '0' : s[i] - 'A' + 10);
}
cout << (m + n >= fac ? S(fac) - S(m - 1) + S(m + n - fac - 2) : S(m + n - 1) - S(m - 1)) << '\n';
}
return 0;
}
F - Heap Partition
情况
- 时间:\(30min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 贪心,\(\text{set}\)
思路
从前往后遍历序列,对于当前元素,尝试将其插入到已有的堆中。如果当前元素无法插入任何已有堆,则创建一个新堆。使用 \(\text{set}\) 维护堆的节点序列,利用 upper_bound 快速找到插入位置。如果一个节点已经有两个子节点,则从 \(\text{set}\) 中移除该节点即可,这样就可以在 \(O(n\log n)\) 的时间里解决了。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e5 + 10;
int a[kMaxN], deg[kMaxN], t, n, cnt;
vector<int> v[kMaxN];
struct node {
int sum, pos;
friend bool operator<(node x, node y) {
return a[x.pos] == a[y.pos] ? x.pos < y.pos : a[x.pos] < a[y.pos];
}
};
set<node> s;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; s.clear(), cnt = 0) {
cin >> n, fill(deg, deg + 1 + n, 0);
node tmp;
for (int i = 1; i <= n; i++) {
cin >> a[i], tmp.pos = i;
auto w = s.upper_bound(tmp);
if (w == s.begin()) {
v[cnt].push_back(tmp.pos), tmp.sum = cnt, s.insert(tmp), cnt++;
} else {
w--, tmp = *w, deg[tmp.pos]++, (deg[tmp.pos] == 2) && (s.erase(w), 0), tmp.pos = i, s.insert(tmp), v[tmp.sum].push_back(i);
}
}
cout << cnt << '\n';
for (int i = 0; i < cnt; i++) {
cout << v[i].size();
for (int j = 0; j < v[i].size(); j++) {
cout << ' ' << v[i][j];
}
cout << '\n', v[i].clear();
}
}
return 0;
}
G - Yet Another Game of Stones
情况
- 时间:\(40min\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 博弈论
思路
这道题我们需要分类讨论:
- \(a_i\) 是奇数,\(b_i=2\),无论如何A都输;
- \(a_i\) 是偶数,\(b_i=2\),A 一定要在第一次就全取完这一堆,否则 B 可以把这一堆变成情况 \(1\);
- \(a_i\) 是奇数且大于一个,\(b_i=1\),A 如果不一次取完,B 可以取得剩下 \(2\) 个,使得 A 输;
- \(a_i\) 是偶数,\(b_i=1\)。A 如果不取得剩下 \(1\) 个, B 可以取得剩下 \(2\) 个,使得 A 输;
- \(b_i=0\),两人可以取任意数量,所以题目变成了普通的 \(\text{nim}\) 博弈。
所以这道题就做完了。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e5 + 10;
int a[kMaxN], b[kMaxN], t, n, sum, cnt1, cnt2;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; sum = cnt1 = cnt2 = 0) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
for (int i = 1; i <= n; i++) {
cnt2 += a[i] & 1 && b[i] == 2;
if ((a[i] > 1 && b[i] == 1) || (!(a[i] & 1) && b[i] == 2)) {
cnt1++, a[i] = !(a[i] & 1) && b[i] == 1;
}
}
if (cnt2 || cnt1 >= 2) {
cout << "Bob\n";
} else {
for (int i = 1; i <= n; i++) {
sum = sum ^ a[i];
}
cout << ((!cnt1 && sum) || (cnt1 && !sum) ? "Alice\n" : "Bob\n");
}
}
return 0;
}
H - Binary Tree Restoring
情况
- 时间:\(1.5h\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- \(\text{dfs}\)
思路
我们可以直接对 \(\text{dfs}\) 结果序列进行模拟,可以类似的参考树的前、中、后序互相转换的递归思路,所以只可能有两种情况:
- 当前节点在两个串中后面的节点假如不同则能确认两个子树;
- 如果相同则把下个点作当前点的一个儿子,如果子树中还有未连根的点则接到当前点下。
所以用深搜的方式不断分割左子树和右子树即可。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 1e6 + 10;
int a[kMaxN], b[kMaxN], f1[kMaxN], f2[kMaxN], ans[kMaxN], t, n, cnt;
void S(int pos, int l, int r, int fa) {
if (l <= r) {
if (a[pos] == b[l]) {
ans[a[pos]] = fa, cnt++, S(pos + 1, l + 1, r, a[pos]);
if (cnt - pos <= r - l) {
ans[a[cnt]] = fa, cnt++, S(cnt, l + cnt - pos, r, a[cnt - 1]);
}
} else {
ans[a[pos]] = fa, cnt++, S(pos + 1, f2[a[pos]] + 1, f2[a[pos]] + f1[b[l]] - pos - 1, a[pos]);
ans[b[l]] = fa, cnt++, S(f1[b[l]] + 1, l + 1, f2[a[pos]] - 1, b[l]);
}
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> t; t--; cout << '\n') {
cin >> n, cnt = 1;
for (int i = 1; i <= n; i++) {
cin >> a[i], f1[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
cin >> b[i], f2[b[i]] = i;
}
S(1, 1, n, 0);
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
}
return 0;
}
K - Final Defense Line
情况
- 时间:\(1h\)
- 预期:\(\text{AC}\)
- 实际:\(\text{AC}\)
知识点
- 数学
思路
我们假设答案为 \(x,y,r\),所以根据题目的描述发现这道题就是要解一个方程:
所以解方程就行了。
代码
#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const double kEps = 1e-9;
int x[3], y[3], r[3], T;
int C(double a) { return a < -kEps ? -1 : (a > kEps ? 1 : 0); }
void S(double A, double B, double &a, double &b) {
double d = sqrt(A * A + B * B), nxt_a = A / d * a + B / d * b, nxt_b = A / d * b - B / d * a;
a = nxt_a, b = nxt_b;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (cin >> T; T--;) {
for (int i = 0; i < 3; i++) {
cin >> x[i] >> y[i] >> r[i];
}
double x1 = x[1] - x[0], y1 = y[1] - y[0], x2 = x[2] - x[0], y2 = y[2] - y[0];
S(x1, y1, x2, y2), S(x1, y1, x1, y1);
if (!C(y2)) {
double a = 2 * (r[1] - r[0]) / x1 - 2 * (r[2] - r[0]) / x2, b = x1 + (r[0] * r[0] - r[1] * r[1]) / x1 - x2 - (r[0] * r[0] - r[2] * r[2]) / x2;
if (!C(a)) {
cout << (C(b) ? "0\n" : "-1\n");
} else {
double ans = -b / a;
if (C(ans - max({0ll, r[0], r[1], r[2]})) < 0) {
cout << "0\n";
} else {
double num = 2 * (r[1] - r[0]) * ans + x1 * x1 - r[1] * r[1] + r[0] * r[0];
num /= 2 * x1;
if (C((ans - r[0]) * (ans - r[0]) - num * num) < 0) {
cout << "0\n";
} else if (C((ans - r[0]) * (ans - r[0]) - num * num)) {
cout << "2 " << fixed << setprecision(12) << ans << '\n';
} else {
cout << "1 " << fixed << setprecision(12) << ans << '\n';
}
}
}
} else {
double a1 = -2 * x1, c1 = 2 * (r[1] - r[0]), d1 = x1 * x1 - r[1] * r[1] + r[0] * r[0], a2 = -2 * x2, b2 = -2 * y2, c2 = 2 * (r[2] - r[0]), d2 = x2 * x2 + y2 * y2 - r[2] * r[2] + r[0] * r[0];
double X1 = -d1 / a1, Y1 = -c1 / a1, X2 = (a2 * d1 - a1 * d2) / (a1 * b2), Y2 = (a2 * c1 - a1 * c2) / (a1 * b2);
double a = Y1 * Y1 + Y2 * Y2 - 1, b = (X1 * Y1 + X2 * Y2 + r[0]) * 2, c = X1 * X1 + X2 * X2 - r[0] * r[0];
if (!C(a) && !C(b) && !C(c)) {
cout << "-1\n";
} else if (!C(a)) {
if (!C(b)) {
cout << "0\n";
} else {
double ans = -c / b;
if (C(ans - max({0ll, r[0], r[1], r[2]})) < 0) {
cout << "0\n";
} else {
cout << "1 " << fixed << setprecision(12) << ans << '\n';
}
}
} else {
double d = b * b - 4 * a * c;
if (C(d) < 0) {
cout << "0\n";
} else if (!C(d)) {
double ans = -b / (2 * a);
if (C(ans - max({0ll, r[0], r[1], r[2]})) < 0) {
cout << "0\n";
} else {
cout << "1 " << fixed << setprecision(12) << ans << '\n';
}
} else {
double ans1 = (-b - sqrt(d)) / (2 * a), ans2 = (-b + sqrt(d)) / (2 * a);
(ans1 > ans2) && (swap(ans1, ans2), 0), (!C(ans1 - ans2)) && (ans1 = -1);
if (C(ans2 - max({0ll, r[0], r[1], r[2]})) < 0) {
cout << "0\n";
} else if (C(ans1 - max({0ll, r[0], r[1], r[2]})) < 0) {
cout << "1 " << fixed << setprecision(12) << ans2 << '\n';
} else {
cout << "2 " << fixed << setprecision(12) << ans1 << '\n';
}
}
}
}
}
return 0;
}

浙公网安备 33010602011771号