2023牛客寒假算法基础集训营4 A-M
A
题解
知识点:数学。
算一下发现 最好, 并列, 以后递减。于是,特判 ,其他取最小值。
(众所周知, 进制最好qwq。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int x, y; cin >> x >> y; cout << (x == 3 || y == 3 ? 3 : min(x, y)) << '\n'; return 0; }
B
题解
知识点:数论,构造。
注意到:
是个素数,显然当 时一定有解。因为 的逆元一定存在,故无论 计算结果为多少, 一定可求。我们可以通过 计算结果,是偶数直接除以 ,否则加减一次 ,就可以得到 。
时,就不一定有解,因为同余式右侧恒为 ,要先满足相加减以后的余数为 否则无解。如果有解,那我们可以令 。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int c[100007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for (int i = 0;i < n;i++) cin >> c[i]; bool ok = 1; if (m == 2) for (int i = 0;i < n;i++) ok &= !((c[i] + c[n - 1 - i]) & 1); if (!ok) cout << "NO" << '\n'; else { for (int i = 0;i < n;i++) { int t = c[i] + c[n - i - 1]; cout << (t % 2 ? (t - m) / 2 : t / 2) << " \n"[i == n - 1]; } for (int i = 0;i < n;i++) { int t = c[i] - c[n - i - 1]; cout << (t % 2 ? (t + m) / 2 : t / 2) << " \n"[i == n - 1]; } } cout << "YES" << '\n'; return 0; }
CD
题解
知识点:背包dp,枚举。
通常背包dp就是从 开始到 直接一个状态跑完,但这道题需要知道一定选和一定不选第 件物品带来的价值之差,从而得到使第 件物品一定选的价值严格大于一定不选的价值。
我们为了得到除了 以外的其他物品的选择情况,考虑设 分别为考虑 / 的物品且总体积不超过 的最大价值。转移方程和普通背包dp一样就不写了。
对于第 个物品,我们求 表示除去第 个物品的最大贡献,以及 表示一定选第 个物品的最大贡献。随后, 即让一定选的价值超过不选的价值,那么 就变成必选物品了。
(C题纯暴力枚举每个物品不选的情况都背包dp一遍,就不写了。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll f[5007][5007], g[5007][5007]; ll v[5007], w[5007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for (int i = 1;i <= n;i++) cin >> v[i] >> w[i]; for (int i = 1;i <= n;i++) { for (int j = 0;j <= m;j++) { if (j < v[i]) f[i][j] = f[i - 1][j]; else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); } } for (int i = n;i >= 1;i--) { for (int j = 0;j <= m;j++) { if (j < v[i]) g[i][j] = g[i + 1][j]; else g[i][j] = max(g[i + 1][j], g[i + 1][j - v[i]] + w[i]); } } for (int i = 1;i <= n;i++) { ll a = 0, b = 0; for (int j = 0;j <= m;j++) a = max(a, f[i - 1][j] + g[i + 1][m - j]); for (int j = 0;j <= m - v[i];j++) b = max(b, f[i - 1][j] + g[i + 1][m - v[i] - j] + w[i]); cout << max(0LL, a - b + 1) << '\n'; } return 0; }
E
题解
知识点:模拟。
直接模拟记录攻击次数 ,分类讨论:
- ,则一次能死的次数加一。
- 否则,若 则无法击杀。否则,次数为 ,表示先攻击一次,后面每次攻击前都会恢复 ,所以下一次攻击有效伤害为 。
最后时间为 。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; ll t, a; cin >> n >> t >> a; vector<pair<ll, ll>> mon; for (int i = 1;i <= n;i++) { ll h, v; cin >> h >> v; mon.push_back({ h,v }); } ll cnt = 0; for (auto [h, v] : mon) { if (h <= a) cnt++; else { if (a <= t * v) { cout << -1 << '\n'; return 0; } cnt += (h - a + a - t * v - 1) / (a - t * v) + 1; } } cout << 1 + (cnt - 1) * t << '\n'; return 0; }
F
题解
知识点:树,DFS,位运算。
先得到两个性质:
- 对于 ,其左右孩子满足 ,因此如果
lowbit
处有连续两个 则是右孩子,否则为左孩子。 - 对于 为根的子树(除了 ),节点数等于 。
对于答案计数,可以考虑 爬到 ,中间分类讨论走两侧的不同情况。
先序遍历:
- 先算上自己,答案加 。
- 是 的左孩子,那么答案加 。
- 是 的右孩子,那么答案加上左子树节点数再加一。
中序遍历:即 。
后序遍历:
- 先加上 为根的子树,答案加节点数。
- 是 的左孩子,答案不变。
- 是 的右孩子,那么答案加上左子树节点数。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int k, q; ll f(ll x) { return x & -x; } ll fo(ll x) { ll ans = 1; while (x != (1LL << k)) { ll fa; if ((x >> 1) & f(x)) { fa = x - f(x); ans += 2 * f(fa - f(x)); } else { fa = x + f(x); ans++; } x = fa; } return ans; } ll lo(ll x) { ll ans = x == (1LL << k) ? x : 2 * f(x) - 1; while (x != (1LL << k)) { ll fa; if ((x >> 1) & f(x)) { fa = x - f(x); ans += 2 * f(fa - f(x)) - 1; } else fa = x + f(x); x = fa; } return ans; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> k >> q; while (q--) { ll x; cin >> x; cout << fo(x) << ' ' << x << ' ' << lo(x) << '\n'; } return 0; }
GH
题解
知识点:bfs,倍增,二分。
我们先求出 到所有点的最短路 ,因为花费都是 可以直接bfs。
显然,我们可以模拟qcjj从 出发开始走的路径,如果途中 小于等于qcjj走到 的距离,那这个点就是答案,这是EZ版本的解。
但HD版本的访问次数很多,不可能一次次模拟,考虑缩短每次模拟的次数。qcjj实际上没有必要一步一步走,假设我们知道第 步qcjj的坐标 ,如果 ,那一定有 ,否则一定有 ,因此答案是符合单调性的。
虽然可以二分答案了,但如何快速求出第 步的确切坐标就成了问题。我们考虑使用倍增的思想,求出 表示 为起点第 步的坐标,这样就能在线性复杂度预处理,对数复杂度求出确切坐标。
进一步思考,既然以及用倍增了,那再套一层二分就没必要了。因为两者都是折半查找,不过前者是从某一端逼近答案,后者是缩小区间锁定答案,那我们只用倍增逼近一次就结束了。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; struct node { int x, y; }; int n, m; char dt[807][807]; const int dir[4][2] = { {0,-1},{0,1},{-1,0},{1,0} }; int dis[807][807]; queue<node> q; void bfs(node s) { for (int i = 0;i < n;i++) for (int j = 0;j < m;j++) dis[i][j] = -1; dis[s.x][s.y] = 0; q.push(s); while (!q.empty()) { auto [x, y] = q.front(); q.pop(); for (int i = 0;i < 4;i++) { int xx = x + dir[i][0]; int yy = y + dir[i][1]; if (~dis[xx][yy] || dt[xx][yy] == '#') continue; dis[xx][yy] = dis[x][y] + 1; q.push({ xx,yy }); } } } map<char, int> mp = { {'L',0},{'R',1},{'U',2},{'D',3} }; node pos[20][807][807]; void pos_init() { for (int i = 0;i < n;i++) { for (int j = 0;j < m;j++) { if (dt[i][j] == '#') pos[0][i][j] = { -1,-1 }; if (dt[i][j] == '.') pos[0][i][j] = { i,j }; else { int xx = i + dir[mp[dt[i][j]]][0]; int yy = j + dir[mp[dt[i][j]]][1]; if (xx < 0 || xx >= n || yy < 0 || yy >= m || dt[xx][yy] == '#') pos[0][i][j] = { i,j }; else pos[0][i][j] = { xx,yy }; } } } for (int k = 1;k < 20;k++) { for (int i = 0;i < n;i++) { for (int j = 0;j < m;j++) { auto [x, y] = pos[k - 1][i][j]; pos[k][i][j] = pos[k - 1][x][y]; } } } } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; int sx, sy; cin >> sx >> sy; node s = { sx,sy }; int q; cin >> q; for (int i = 0;i < n;i++) for (int j = 0;j < m;j++) cin >> dt[i][j]; bfs(s); pos_init(); while (q--) { int xt, yt; cin >> xt >> yt; node t = { xt,yt }; int ans = 0; for (int i = 19;i >= 0;i--) { auto [x, y] = pos[i][t.x][t.y]; if (!~dis[x][y] || dis[x][y] > ans + (1 << i)) { t = { x,y }; ans += 1 << i; }//不能等于,ans的意义是不可行的最大值;加了等于意义就乱了 } cout << (++ans == 1 << 20 ? -1 : ans) << '\n'; } return 0; }
I
题解
知识点:莫队,离线。
只需要构造 长度的序列,明示了使用莫队算法。具体的说,对没ban掉的方向的坐标当作左端点按 分块,另一个方向的坐标当作右端点即可。
实现需要一点(很烦的)分类讨论。
(没学过莫队的可以速通一下了qwq。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int n, m; char X; vector<pair<int, int>> pos; void calc(char U, char D, char R) { int sz = sqrt(n); sort(pos.begin(), pos.end(), [&](pair<int, int> a, pair<int, int> b) { if (a.first / sz == b.first / sz) return a.second < b.second; else return a.first / sz < b.first / sz; }); string ans; int x = 0, y = 0; for (auto [px, py] : pos) { while (y != py) (y += 1) %= n, ans += R; while (x < px) (x += 1) %= n, ans += D; while (x > px) (x -= 1 - n) %= n, ans += U; } cout << ans << '\n'; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m >> X; for (int i = 1;i <= m;i++) { int x, y; cin >> x >> y; pos.push_back({ x,y }); } if (X == 'L') calc('U', 'D', 'R'); else if (X == 'R') { for (auto &[x, y] : pos) y = (n - y) % n; calc('U', 'D', 'L'); } else if (X == 'U') { for (auto &[x, y] : pos) swap(x, y); calc('L', 'R', 'D'); } else { for (auto &[x, y] : pos) swap(x, y), y = (n - y) % n; calc('L', 'R', 'U'); } return 0; }
J
题解
方法一
知识点:拓扑排序,枚举。
因为保证一定符合某一种解,那关系图一定是DAG。
我们知道拓扑排序能在不破坏DAG节点顺序下,求出节点的相对顺序。因此我们可以使用拓扑排序,同时记录节点位置 。通过 ,我们可以得到一个节点的位置上下确界 ,显然如果节点 是确定的那么这个区间只会包括 ,如果不是,那么被这个区间包括的所有点都是不能确定的。因此,我们可以对所有点枚举边界即可排除不确定的点。
时间复杂度
空间复杂度
方法二
知识点:dfs,STL,枚举。
考虑求一个传递闭包,从而可以直接得到一个点与所有点的关系。因为图是DAG,我们可以通过dfs遍历图获得,不需要floyd。
最后,对每个点记录小于等于自己和大于等于自己的个数和,如果等于 那说明这个点和其他 个点的关系完全确定,就能知道位置了。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; int n, m; vector<int> g[1007]; int deg[1007]; queue<int> q; int ans[1007]; int pos[1007], cnt; void toposort() { for (int i = 1;i <= n;i++) if (!deg[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); cnt++; ans[cnt] = u; pos[u] = cnt; for (auto v : g[u]) { deg[v]--; if (!deg[v]) q.push(v); } } } int maxl[1007], minr[1007]; int b[1007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= m;i++) { int u, v; cin >> u >> v; g[u].push_back(v); deg[v]++; } toposort(); for (int i = 1;i <= n;i++) minr[i] = n + 1; for (int i = 1;i <= n;i++) { for (auto v : g[i]) { minr[i] = min(minr[i], pos[v]); maxl[v] = max(maxl[v], pos[i]); } } for (int i = 1;i <= n;i++) b[i] = ans[i]; int r = 1; for (int i = 1;i <= n;i++) { if (r > i) b[i] = -1; r = max(r, minr[ans[i]]); } int l = n; for (int i = n;i >= 1;i--) { if (l < i) b[i] = -1; l = min(l, maxl[ans[i]]); } for (int i = 1;i <= n;i++)cout << b[i] << " \n"[i == n]; return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; int n, m; vector<int> g[1007]; bool vis[1007]; bitset<1007> bs[1007]; void dfs(int u) { if (vis[u]) return; vis[u] = 1; for (auto v : g[u]) { dfs(v); bs[u] |= bs[v]; } } int b[1007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= n;i++) bs[i][i] = 1, b[i] = -1; for (int i = 1;i <= m;i++) { int u, v; cin >> u >> v; g[u].push_back(v); } for (int i = 1;i <= n;i++) if (!vis[i]) dfs(i); for (int i = 1;i <= n;i++) { int cnt = 0; for (int j = 1;j <= n;j++) cnt += bs[j][i]; if (bs[i].count() + cnt == n + 1) b[cnt] = i; } for (int i = 1;i <= n;i++)cout << b[i] << " \n"[i == n]; return 0; }
K
题解
知识点:概率dp,贪心。
我们考虑定义状态 ,表示未知牌有 个,没配对的已知牌有 个。
先得到一个结论:
- 当 时,每次操作翻两个未知牌是最优的。因为每次操作都能贡献 个已知,而用翻过的牌配对未知的,直接配对概率很小,其他情况只贡献一个未知。
- 当 时,特判,也可以暴力枚举。
接下来考虑 转移,每次翻两个未知的情况,注意 且 与 同奇偶:
- 翻出来两个已知未配对的,那么可以额外花费两次操作消除,已知未配对的牌数减 。
- 翻出来一个已知未配对的,另一个未知,那么可以额外花费一次消除。
- 翻出来两个相同的未知,可以直接消除。
- 翻出来两个不同的未知,则已知未配对的牌数加 。
概率和转移可以看代码。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; const int P = 1e9 + 7; struct Modint { int val; Modint(int _val = 0):val(_val %P) { format(); } Modint(ll _val):val(_val %P) { format(); } //if val in [-P,2P) //maybe slower than global version Modint &format() { if (val < 0) val += P; if (val >= P) val -= P; return *this; } Modint inv()const { return qpow(*this, P - 2); } Modint &operator+=(const Modint &x) { val += x.val;return format(); } Modint &operator-=(const Modint &x) { val -= x.val;return format(); } Modint &operator*=(const Modint &x) { val = 1LL * val * x.val % P;return *this; } Modint &operator/=(const Modint &x) { return *this *= x.inv(); } friend Modint operator-(const Modint &x) { return { -x.val }; } friend Modint operator+(Modint a, const Modint &b) { return a += b; } friend Modint operator-(Modint a, const Modint &b) { return a -= b; } friend Modint operator*(Modint a, const Modint &b) { return a *= b; } friend Modint operator/(Modint a, const Modint &b) { return a /= b; } friend Modint qpow(Modint a, ll k) { Modint ans = 1; while (k) { if (k & 1) ans = ans * a; k >>= 1; a = a * a; } return ans; } friend istream &operator>>(istream &is, Modint &x) { ll _x; is >> _x; x = { _x }; return is; } friend ostream &operator<<(ostream &os, const Modint &x) { return os << x.val; } }; int inv[2507]; void init(int n) { inv[1] = 1; for (int i = 2;i <= n;i++) inv[i] = 1LL * (P - P / i) * inv[P % i] % P; } Modint f[2507][2507]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; map<int, int> mp; for (int i = 1;i <= n;i++) { for (int j = 1;j <= m;j++) { int x; cin >> x; mp[x]++; } } array<int, 3> c{}; c[0] = mp[0]; for (auto [x, y] : mp) if (x) c[y]++; int N = n * m; init(N); f[0][0] = 0; f[1][1] = 1; f[2][0] = 1; f[2][2] = Modint(5) * inv[2]; for (int i = 3;i <= N;i++) { for (int j = i;j >= 0;j -= 2) { /* p1:两个翻到过的 p2:一个翻到过的,一个没翻到过的(注意选择顺序会影响概率) p3:两个相同的没翻到过的 p4:两个不同的没翻到过的 */ Modint p1 = Modint(j) * inv[i] * (j - 1) * inv[i - 1]; Modint p2 = Modint(j) * inv[i] * (i - j) * inv[i - 1] * 2; Modint p3 = Modint(i - j) * inv[i] * inv[i - 1]; Modint p4 = Modint(i - j) * inv[i] * (i - j - 2) * inv[i - 1]; f[i][j] = 1; f[i][j] += p1 * (2 + f[i - 2][j - 2]); f[i][j] += p2 * (1 + f[i - 2][j]); f[i][j] += p3 * f[i - 2][j]; f[i][j] += p4 * f[i - 2][j + 2]; } } cout << c[2] + f[c[0]][c[1]] << '\n'; return 0; }
L
题解
知识点:数学。
可以先求出 ,再求出 ,随后复制一份排序判断,因为输出要按原来顺序。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int A, B, C; cin >> A >> B >> C; if ((A - B + C) & 1) return false; vector<int> v(4); v[2] = (A - B + C) / 2; v[3] = A - v[2]; v[1] = C - v[2]; auto g = v; sort(g.begin() + 1, g.end()); if (g[1] <= 0 || g[1] + g[2] <= g[3]) return false; cout << "YES" << '\n'; cout << v[1] << ' ' << v[2] << ' ' << v[3] << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << "NO" << '\n'; } return 0; }
M
题解
知识点:构造。
众所周知, 是构造不出三角形的,循环打就行。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 0;i < n;i++) cout << "123"[i % 3] << ' '; cout << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17087025.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)