写在前面:最后一题比较有意思,使用了bitset优化,没想到Rc也喜欢考。
RC-u1 智能红绿灯
思路:
签到题。根据题目要求打好标记就好了,只需要注意一些corner case,如刚按下按钮的时刻、红灯变绿灯的时刻以及绿灯变红灯的时刻。
时间复杂度: O ( m a x ( t i m e ) + N ) O(max(time) + N) O(max(time)+N)
代码:
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int N;
std::cin >> N;
std::vector<int> a(N);
for (int i = 0; i < N; i++) {
std::cin >> a[i];
}
int ok = 0;
int cur = 0;
int nxt1 = -1, nxt2 = -1;
// std::vector<int> stk;
std::vector<std::pair<int, int>> ans;
for (int time = 0; time <= 1E5; time++) {
if (time == nxt1) {
// stk.push_back(time + 15);
ok = 2;
}
while (cur < N && a[cur] == time) {
if (!ok) {
ok = 1;
nxt1 = time + 15;
nxt2 = time + 44;
} else if (ok == 2) {
ok = 3;
nxt2 += 15;
}
cur++;
}
if (time == nxt2) {
ans.push_back({nxt1, nxt2});
nxt2 = -1;
nxt1 = -1;
ok = 0;
}
}
for (auto [x, y] : ans) {
std::cout << x << " " << y << "\n";
}
return 0;
}
RC-u2 女王的大敕令
思路:注意到小怪只会攻击我们两次,所以实际上我们只要枚举两次状态就好了,在这之前我们需要计算出小怪会攻击的行和列,同时也要判断枚举的状态是否满足步长。
时间复杂度: O ( 5 4 ) O(5^4) O(54)
代码:
#include <bits/stdc++.h>
using i64 = long long;
int distance(int x1, int y1, int x2, int y2) {
return std::abs(x1 - x2) + std::abs(y1 - y2);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int C1, C2, R1, R2;
std::cin >> C1 >> C2 >> R1 >> R2;
C1--, C2--, R1--, R2--;
std::vector<int> n(4);
for (int i = 0; i < 4; i++) {
std::cin >> n[i];
}
int row, col, D1, D2;
std::cin >> row >> col >> D1 >> D2;
row--, col--;
std::array<int, 4> first{C1, C2, R1 - n[2], R2 + n[3]}, second{C1 + n[0], C2 - n[1], R1 - n[2], R2 + n[3]};
for (int x1 = 0; x1 < 5; x1++) {
for (int y1 = 0; y1 < 5; y1++) {
for (int x2 = 0; x2 < 5; x2++) {
for (int y2 = 0; y2 < 5; y2++) {
if (distance(x1, y1, x2, y2) == D1 && distance(x2, y2, row, col) == D2) {
if (y1 == first[1] || y1 == first[0] || x1 == first[3] || x1 == first[2]) {
continue;
}
if (y2 == second[1] || y2 == second[0] || x2 == second[3] || x2 == second[2]) {
continue;
}
std::cout << x1 + 1 << " " << y1 + 1 << " " << x2 + 1 << " " << y2 + 1 << "\n";
}
}
}
}
}
return 0;
}
RC-u3 战利品分配
思路:注意到边权都为1,那么我们可以直接进行bfs,进而就能获取到从源点S到汇点T的所有路径。读者可以考虑为什么不使用Dijkstra。这样再队列中额外加入价值这一属性就可以进行求解了。需要注意的是,本题也有corner case,如玩家排在第一位,就需要将源点S的价值加进来,另外还需要注意记录最短路径的值(因为有多条到汇点T的路径)。
时间复杂度: O ( N + M ) O(N + M) O(N+M)
代码:
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int N, M, K, P;
std::cin >> N >> M >> K >> P;
P--;
std::vector<int> p(N);
for (int i = 0; i < N; i++) {
std::cin >> p[i];
}
std::vector<std::vector<int>> adj(N);
for (int i = 0; i < M; i++) {
int U, V;
std::cin >> U >> V;
U--, V--;
adj[U].push_back(V);
adj[V].push_back(U);
}
int S, T;
std::cin >> S >> T;
S--, T--;
std::vector<int> dis(N, -1);
std::queue<std::pair<int, i64>> q;
q.push({S, (P == 0 ? p[S] : 0)});
dis[S] = 0;
i64 ans = 0;
int minDis = -1;
while (!q.empty()) {
auto [x, val] = q.front();
q.pop();
if (x == T && dis[x] == minDis) {
ans = std::max(ans, val);
continue;
}
for (auto y : adj[x]) {
if (dis[y] == -1 || y == T) {
if (y == T) {
if (dis[y] == -1) {
minDis = dis[x] + 1;
} else if (dis[x] + 1 != minDis) {
continue;
}
}
dis[y] = dis[x] + 1;
if (dis[y] % K == P) {
q.push({y, val + p[y]});
} else {
q.push({y, val});
}
}
}
}
std::cout << ans << "\n";
return 0;
}
RC-u4 变牛的最快方法
思路:其实这是一道最短编辑距离的板子题,只不过本题需要记录每一步的操作。记dp[i][j]
为A序列前i位变为B序列前j位的最短编辑距离,op[i][j]
就为每一步对应的操作,pre[i][j]
代表dp[i][j]
是从哪个状态转移过来的,用于后续输出具体方案。
下面考虑状态转移:对于删除操作,说明A的前i - 1位已经和B的j位匹配了,故dp[i][j] = dp[i - 1][j] + 1
;插入操作,A的前i位已经和B的j - 1位匹配,故dp[i][j] = dp[i][j - 1] + 1
;修改操作,A的前i - 1位已经和B的j - 1位匹配,故dp[i][j] = dp[i - 1][j - 1] + (A[i] == B[j])
。三种情况取最小值即可。
接下来就是考虑初始化问题了,对于dp[0][i]
与dp[i][0]
是需要我们进行初始化的,op
与pre
也是如此。
时间复杂度: O ( N M ) O(NM) O(NM)
代码:
#include <bits/stdc++.h>
using i64 = long long;
constexpr int inf = 1E9;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<int> A{0}, B{0};
int x;
while (std::cin >> x) {
if (x == -1) {
break;
}
A.push_back(x);
}
while (std::cin >> x) {
if (x == -1) {
break;
}
B.push_back(x);
}
int N = A.size();
int M = B.size();
std::vector dp(N, std::vector<int>(M, inf));
std::vector op(N, std::vector<int>(M));
std::vector pre(N, std::vector<std::pair<int, int>>(M));
for (int i = 0; i < N; i++) {
dp[i][0] = i;
op[i][0] = 0;
if (i) {
pre[i][0] = {i - 1, 0};
}
}
for (int i = 0; i < M; i++) {
dp[0][i] = i;
op[0][i] = 3;
if (i) {
pre[0][i] = {0, i - 1};
}
}
for (int i = 1; i < N; i++) {
for (int j = 1; j < M; j++) {
int add = dp[i][j - 1] + 1;
int del = dp[i - 1][j] + 1;
int modify = dp[i - 1][j - 1] + !(A[i] == B[j]);
dp[i][j] = std::min({add, del, modify});
if (dp[i][j] == add) {
op[i][j] = 3;
pre[i][j] = {i, j - 1};
} else if (dp[i][j] == del) {
op[i][j] = 0;
pre[i][j] = {i - 1, j};
} else {
op[i][j] = (A[i] == B[j]) ? 2 : 1;
pre[i][j] = {i - 1, j - 1};
}
}
}
std::cout << dp[N - 1][M - 1] << "\n";
std::vector<int> ans;
std::pair<int, int> state = {N - 1, M - 1};
while (1) {
auto [lhs, rhs] = state;
if (!lhs && !rhs) {
break;
}
// debug(state);
ans.push_back(op[lhs][rhs]);
state = pre[lhs][rhs];
}
std::reverse(ans.begin(), ans.end());
for (auto x : ans) {
std::cout << x;
}
return 0;
}
RC-u5 养老社区
思路:首先看到题目给的数据是一颗树,因此可以在 O ( n 2 ) O(n^2) O(n2)的时间复杂度内将树上任意两点间的距离求出来。一个比较暴力的想法是我们可以 O ( n 3 ) O(n^3) O(n3)地枚举每三个点的组合,本题由于数据比较水并没有卡这种做法,所以实际上是可以跑过去的。
我们考虑优化,出于经验,这类多层循环暴力枚举的问题我们或许可以进行bitset优化,那么时间复杂度降为 O ( n 3 w ) O(\frac{n^3}{w}) O(wn3),可以轻松通过该题目。
考虑可行性:直观上想,我们先可以枚举两个点,剩下的一个点使用bitset进行优化,即使用bitset来刻画“对于某一点,剩下的点中可用点的集合”,本题是有两个限定条件的,即长度与种类,所以实际上需要开两种bitset。由于长度限制,我们将当前正枚举的两个点按照长度放到不同数组中,对于每一个长度,f1[u] & f1[v] & f[T[u]] & f[T[v]]
中1的个数(集合大小)即为答案,除以3得到最终答案,因为三元组被重复计算了三遍。
时间复杂度: O ( n 3 w ) ,其中 ( w = 64 ) O(\frac{n^3}{w}),其中(w=64) O(wn3),其中(w=64)
代码:
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int N;
std::cin >> N;
std::vector<std::vector<int>> adj(N);
for (int i = 0; i < N - 1; i++) {
int U, V;
std::cin >> U >> V;
U--, V--;
adj[U].push_back(V);
adj[V].push_back(U);
}
constexpr int M = 2E3;
std::bitset<M> f[N];
for (int i = 0; i < N; i++) {
f[i] = ~f[i];
}
std::vector<int> T(N);
for (int i = 0; i < N; i++) {
std::cin >> T[i];
T[i]--;
f[T[i]].set(i, false);
}
std::vector dis(N, std::vector<int>(N, -1));
for (int i = 0; i < N; i++) {
std::queue<int> q;
q.push(i);
dis[i][i] = 0;
while (!q.empty()) {
int x = q.front();
q.pop();
for (auto y : adj[x]) {
if (dis[i][y] == -1) {
dis[i][y] = dis[i][x] + 1;
q.push(y);
}
}
}
}
std::vector<std::vector<std::pair<int, int>>> S(N + 1);
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
S[dis[i][j]].push_back({i, j});
}
}
i64 ans = 0;
for (int d = 0; d <= N; d++) {
std::bitset<M> f1[N];
for (auto [u, v] : S[d]) {
f1[u].set(v, true);
f1[v].set(u, true);
}
for (auto [u, v] : S[d]) {
if (T[u] == T[v]) {
continue;
}
ans += (f1[u] & f1[v] & f[T[u]] & f[T[v]]).count();
}
}
std::cout << ans / 3 << "\n";
return 0;
}