牛客小白月赛27
比赛链接:https://ac.nowcoder.com/acm/contest/6874#question
A - 巨木之森
题解
除了起点到终点的路径,其他的路径都会走两遍,所以为了尽可能减少折返的路径,每次应使起点终点所在路径尽可能的长,亦即二者距离尽可能的远。
最长的一条路径为树的直径,根据树的直径的性质,任意一点的最远点一定为树的直径的两个端点之一。
为了寻找树的直径的两个端点A、B,可以从任意一点BFS得到树的直径的一端点A,再以端点A为起点BFS得到直径的另一端点B。
得到端点A、B后,BFS得到每个点距离A、B的距离,从每个点 $i$ 出发遍历全树的花费即为:
$2\ \times$ 所有路径长 $-\ max(dis_{Ai},dis_{Bi})$
对所有点的花费排序,从小到大采用即可。
代码
#include <bits/stdc++.h> #define int long long using namespace std; signed main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<pair<int, int>>> G(n); int tot = 0; for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; --u, --v; tot += w; G[u].emplace_back(v, w); G[v].emplace_back(u, w); } vector<int> disA(n), disB(n); function<void(int, vector<int> &)> bfs = [&](int s, vector<int> &dist) { vector<bool> vis(n); queue<pair<int, int>> que; que.emplace(s, 0); vis[s] = true; while (!que.empty()) { int u = que.front().first; int dis = que.front().second; que.pop(); dist[u] = dis; for (auto i : G[u]) { int v = i.first; int w = i.second; if (!vis[v]) { que.emplace(v, dis + w); vis[v] = true; } } } }; bfs(0, disA); int A = max_element(disA.begin(), disA.end()) - disA.begin(); bfs(A, disA); int B = max_element(disA.begin(), disA.end()) - disA.begin(); bfs(B, disB); vector<int> wt(n); for (int i = 0; i < n; i++) { wt[i] = tot * 2 - max(disA[i], disB[i]); } sort(wt.begin(), wt.end()); int ans = 0; for (int i = 0; i < n; i++) { m -= wt[i]; if (m >= 0) { ++ans; } else { break; } } cout << ans << "\n"; return 0; }
B - 乐团派对
题解
如果每个乐手都可以被分进一队,那么一定有 $max_{a_i} \le n$ 。
将 $a_i$ 排序,为了保证能力值最大的乐手可以被分进一队,先将他及之前共 $a_n$ 个人分为一队。
之后对于前面的 $n - a_n$ 人进行 $dp$,每个人有两种选择:
- $dp_i = dp_{i - 1}$,加入之前的队伍
- $dp_i = dp_{i - a_i} + 1$,与前面的 $a_i$ 人另成一队
最终答案即为 $dp_{n - a_n} + 1$ 。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i++) cin >> a[i]; sort(a.begin(), a.end()); if (a[n] > n) { cout << -1 << "\n"; return 0; } vector<int> dp(n + 1); for (int i = 1; i <= n - a[n]; i++) { int j = i - a[i]; dp[i] = max(dp[i - 1], (j >= 0 ? dp[j] + 1 : 0)); } cout << dp[n - a[n]] + 1 << "\n"; return 0; }
C - 光玉小镇
题解
因为 T 的数目最多为 15,所以如果枚举所有可能的修理路径,T! 是会超时的,这个时候可以考虑状压 $dp$ 。
首先 BFS 得到每个电线杆离家的距离 $disS$,以及电线杆两两之间的距离 $dis_{ij}$,同时初始化 $dp$ 数组:
for (int i = 0; i < int(T.size()); i++) { dp[1 << i][i] = disS[i] = bfs(Sx, Sy, T[i].first, T[i].second); }
将每个电线杆的修理状态表示为 $dp_{ij}$ 中 $i$ 的一个比特,$j$ 表示为当前状态下最后修的是哪一个电线杆,状态转移方程即为:
for (int i = 0; i < (1 << T.size()); i++) { for (int j = 0; j < int(T.size()); j++) { if (((1 << j) & i) == 0) continue; for (int k = 0; k < int(T.size()); k++) { if ((1 << k) & i) continue; dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + dis[j][k]); } } }
$ans$ 即为:
for (int i = 0; i < int(T.size()); i++) { ans = min(ans, dp[(1 << T.size()) - 1][i] + disS[i] + 1LL * int(T.size()) * t); }
代码
#include <bits/stdc++.h> using namespace std; const int dir[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; struct P{ int x, y, dis; }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, t; cin >> n >> m >> t; vector<vector<char>> MP(n, vector<char> (m)); int Sx = -1, Sy = -1; vector<pair<int, int>> T; for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) { cin >> MP[i][j]; if (MP[i][j] == 'S') { Sx = i, Sy = j; } if (MP[i][j] == 'T') { T.emplace_back(i, j); } } function<bool(int, int)> legal = [&](int x, int y) { return 0 <= x and x < n and 0 <= y and y < m and MP[x][y] != '#'; }; function<int(int, int, int, int)> bfs = [&](int sx, int sy, int ex, int ey) { vector<vector<bool>> vis(n, vector<bool> (m)); queue<P> que; que.push({sx, sy, 0}); vis[sx][sy] = true; while (!que.empty()) { int x = que.front().x; int y = que.front().y; int dis = que.front().dis; que.pop(); if (x == ex and y == ey) { return dis; } for (int i = 0; i < 4; i++) { int nx = x + dir[i][0]; int ny = y + dir[i][1]; if (legal(nx, ny) and !vis[nx][ny]) { que.push({nx, ny, dis + 1}); vis[nx][ny] = true; } } } return -1; }; vector<vector<int>> dp(1 << T.size(), vector<int> (T.size(), 1e9)); vector<int> disS(T.size()); for (int i = 0; i < int(T.size()); i++) { dp[1 << i][i] = disS[i] = bfs(Sx, Sy, T[i].first, T[i].second); if (disS[i] == -1) { cout << -1 << "\n"; return 0; } } vector<vector<int>> dis(T.size(), vector<int> (T.size())); for (int i = 0; i < int(T.size()); i++) { for (int j = i + 1; j < int(T.size()); j++) { dis[i][j] = dis[j][i] = bfs(T[i].first, T[i].second, T[j].first, T[j].second); if (dis[i][j] == -1) { cout << -1 << "\n"; return 0; } } } for (int i = 0; i < (1 << T.size()); i++) { for (int j = 0; j < int(T.size()); j++) { if (((1 << j) & i) == 0) continue; for (int k = 0; k < int(T.size()); k++) { if ((1 << k) & i) continue; dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + dis[j][k]); } } } long long ans = LLONG_MAX; for (int i = 0; i < int(T.size()); i++) { ans = min(ans, dp[(1 << T.size()) - 1][i] + disS[i] + 1LL * int(T.size()) * t); } cout << ans << "\n"; return 0; }
D - 巅峰对决
题解
线段树维护区间最值,如果某个区间的值连续,那么该区间的最值差一定等于区间长度。
代码
#include <bits/stdc++.h> #define lson i << 1, l, mid #define rson i << 1 | 1, mid + 1, r #define mid ((l + r) >> 1) using namespace std; constexpr int N = 1e5 + 100; int mi[N << 2], mx[N << 2], a[N]; void pushup(int i) { mi[i] = min(mi[i << 1], mi[i << 1 | 1]); mx[i] = max(mx[i << 1], mx[i << 1 | 1]); } void build(int i, int l, int r) { if (l == r) { mi[i] = mx[i] = a[l]; return; } build(lson); build(rson); pushup(i); } void update(int i, int l, int r, int x, int p) { if (l == r) { mi[i] = mx[i] = p; return; } if (x <= mid) { update(lson, x, p); } else { update(rson, x, p); } pushup(i); } pair<int, int> query(int i, int l, int r, int L, int R) { if (r < L or l > R) return make_pair(INT_MAX, INT_MIN); if (L <= l and r <= R) return make_pair(mi[i], mx[i]); const pair<int, int> a = query(lson, L, R); const pair<int, int> b = query(rson, L, R); return make_pair(min(a.first, b.first), max(a.second, b.second)); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, q; cin >> n >> q; for (int i = 1; i <= n; i++) cin >> a[i]; build(1, 1, n); for (int i = 0; i < q; i++) { int op, l, r; cin >> op >> l >> r; if (op == 1) { update(1, 1, n, l, r); } else { const pair<int, int> q = query(1, 1, n, l, r); cout << (q.second - q.first == r - l ? "YES" : "NO") << "\n"; } } return 0; }
E - 使徒袭来
题解
基本不等式:
$\frac{a_1 + a_2 + \dots + a_n}{n} \ge \sqrt[n]{a_1 \times a_2 \times \dots \times a_n}$
本题为:
$a_1 + a_2 + a_3 \ge 3 \times \sqrt[3]{a_1 \times a_2 \times a_3}$
代码
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; printf("%.3f\n", 3.0 * pow(n, 1.0 / 3.0)); return 0; }
F - 核弹剑仙
题解一
根据武器的破坏力由弱到强单向建图,枚举出发点,被访问的点的个数即为破坏力强于出发点的武器个数。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<int>> G(n); for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; --u, --v; G[v].push_back(u); } vector<bool> vis(n); function<void(int)> dfs = [&](int u) { if (vis[u]) return; vis[u] = true; for (auto v : G[u]) { dfs(v); } }; for (int i = 0; i < n; i++) { fill(vis.begin(), vis.end(), false); dfs(i); cout << count(vis.begin(), vis.end(), true) - 1 << "\n"; } return 0; }
题解二
根据武器的破坏力由强到弱单向建图拓扑排序,并用 $bitset$ 的状态来记录每个之前遍历到的武器,比当前武器破坏力强的武器个数即为 dp[i].count() 。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<int>> G(n); vector<int> deg(n); for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; --u, --v; G[u].push_back(v); ++deg[v]; } vector<bitset<1005>> dp(n); queue<int> que; for (int i = 0; i < n; i++) { if (deg[i] == 0) que.push(i); } while (!que.empty()) { int u = que.front(); que.pop(); for (auto v : G[u]) { dp[v] |= dp[u]; dp[v].set(u); if (--deg[v] == 0) que.push(v); } } for (int i = 0; i < n; i++) cout << dp[i].count() << " \n"[i == n - 1]; return 0; }
G - 虚空之力
题解一
模拟。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; int a = 0, b = 0, c = 0, d = 0; for (int i = 0; i < n; i++) { char x; cin >> x; if (x == 'k') ++a; if (x == 'i') ++b; if (x == 'n') ++c; if (x == 'g') ++d; } int ans = 0; while (a >= 1 and b >= 2 and c >= 2 and d >= 2) { a -= 1; b -= 2; c -= 2; d -= 2; ans += 2; } while (a >= 1 and b >= 1 and c >= 1 and d >= 1) { a -= 1;; b -= 1; c -= 1; d -= 1; ans += 1; } cout << ans << "\n"; return 0; }
题解二
数学。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; int a = 0, b = 0, c = 0, d = 0; for (int i = 0; i < n; i++) { char x; cin >> x; if (x == 'k') ++a; if (x == 'i') ++b; if (x == 'n') ++c; if (x == 'g') ++d; } int mi = min({a, b / 2, c / 2, d / 2}); cout << 2 * mi + min({a - mi, b - 2 * mi, c - 2 * mi, d - 2 * mi}) << "\n"; return 0; }
H - 社团游戏
题解
二维前缀和 + 二分。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, k; cin >> n >> m >> k; vector<vector<char>> MP(n + 1, vector<char>(m + 1)); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> MP[i][j]; vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(m + 1, vector<int>(26))); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { for (int k = 0; k < 26; k++) { dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - 1][k] - dp[i - 1][j - 1][k] + (MP[i][j] == 'a' + k); } } } function<bool(int, int, int)> ok = [&](int x, int y, int d) { int nx = x + d - 1, ny = y + d - 1; for (int i = 0; i < 26; i++) { if (dp[nx][ny][i] - dp[x - 1][ny][i] - dp[nx][y - 1][i] + dp[x - 1][y - 1][i] > k) { return false; } } return true; }; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { int l = 1, r = min(n - i + 1, m - j + 1); while (r - l > 1) { int mid = (l + r) / 2; if (ok(i, j, mid)) { l = mid; } else { r = mid - 1; } } cout << (ok(i, j, r) ? r : l) << " \n"[j == m]; } } return 0; }
I - 名作之壁
题解
尺取 + 单调队列。
代码
#include <bits/stdc++.h> using namespace std; constexpr int MOD = 1e9; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, k, x, b, c; cin >> n >> k >> x >> b >> c; vector<int> a(n + 1); a[0] = x; long long ans = 0; deque<int> mi, mx; for (int l = 1, r = 1; r <= n; r++) { a[r] = (1LL * a[r - 1] * b + c) % MOD; while (mi.size() and a[mi.back()] >= a[r]) mi.pop_back(); mi.push_back(r); while (mx.size() and a[mx.back()] <= a[r]) mx.pop_back(); mx.push_back(r); while (a[mx.front()] - a[mi.front()] > k) { ans += n - r + 1; ++l; if (mi.front() < l) mi.pop_front(); if (mx.front() < l) mx.pop_front(); } } cout << ans << "\n"; return 0; }
J - 逃跑路线
题解
$(2^1 - 1)\ \& \ (2^2 - 1)\ \& \ \dots \ \& \ (2^n - 1) = 1$
即判断最后横坐标的奇偶性。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; int ans = 0; for (int i = 0; i < n; i++) { string s; cin >> s; ans += (s.back() - '0') & 1; } cout << (ans & 1) << "\n"; return 0; }