Codeforces Round 923 (Div. 3)
写在前面
比赛地址:https://codeforces.com/contest/1927。
一周前的题拖到现在哈哈好爽
A
找到最左和最右的 B
即可。
复制复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n; std::cin >> n; std::string s; std::cin >> s; int l = 0, r = 0; for (int i = 1; i <= n; ++ i) { if (s[i - 1] == 'B') { if (l == 0) l = i; r = i; } } if (l == r && l == 0) std::cout << "0\n"; else std::cout << r - l + 1 << "\n"; } return 0; }
B
保证有解,直接按照题意构造即可。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n; std::cin >> n; int cnt[26] = {0}; for (int i = 1; i <= n; ++ i) { int x; std::cin >> x; for (int j = 0; j < 26; ++ j) { if (cnt[j] == x) { std::cout << (char) (j + 'a'); cnt[j] ++; break; } } } std::cout << "\n"; } return 0; }
C
有解当且仅当仅在数列 一方中出现的 中的权值种类数不大于 。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kK = 1e6 + 10; //============================================================= int n, m, k, a[kN], b[kN]; bool yesa[kK], yesb[kK]; //============================================================= bool Solve() { for (int i = 1; i <= k; ++ i) yesa[i] = yesb[i] = 0; for (int i = 1; i <= n; ++ i) yesa[a[i]] = 1; for (int i = 1; i <= m; ++ i) yesb[b[i]] = 1; int cnta = 0, cntb = 0; for (int i = 1; i <= k; ++ i) { if (!yesa[i] && !yesb[i]) return 0; if (yesa[i] && yesb[i]) continue; if (yesa[i]) ++ cnta; else ++ cntb; } return (2 * cnta <= k) && (2 * cntb <= k); } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n >> m >> k; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; for (int i = 1; i <= m; ++ i) std::cin >> b[i]; std::cout << (Solve() ? "YES\n" : "NO\n"); } return 0; }
D
傻逼题,但是我是傻逼。
首先对于每个位置预处理右侧第一个与其值不同的位置 ,简单倒序枚举即可。
对于一次区间查询 显然令 最有可能有解,仅需检查 是否不大于 即可。
然而赛时把题意抽象得太过了不仅预处理了一坨屎还写了个 ST 表简直是糖丸了
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kM = 1e6 + 10; //============================================================= int n, q, a[kN], next[kN]; //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; a[n + 1] = -1; for (int i = n; i; -- i) { if (a[i + 1] != a[i]) next[i] = i + 1; else next[i] = next[i + 1]; } std::cin >> q; while (q --) { int l, r; std::cin >> l >> r; if (next[l] > r) std::cout << "-1 -1\n"; else std::cout << l << " " << next[l] << "\n"; } } return 0; }
E
要求相邻的长度为 的区间和至多只有两种,则考虑向右进行滑动窗口的过程,说明滑动过程中区间和不断地交替加减 1,说明每次添加与删除的数的差值是交替的 ,看起来是和奇偶性很有关的。
纯手玩有点麻烦于是打了个表发现了这样一种构造方法:考虑将 个位置每 个数作为一段,考虑按顺序每次填入所有段的同一位置 :若 为奇数则从 1 开始递增地填,否则从 开始递减地填。
以 为例:(1 10 4 7) (2 9 5 6) (3 8
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; //============================================================= int n, k, ans[kN]; //============================================================= void Solve() { int now1 = 1, now2 = n; for (int i = 1; i <= k; ++ i) { for (int j = i; j <= n; j += k) { if (i % 2 == 1) ans[j] = now1 ++; else ans[j] = now2 --; } } } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n >> k; Solve(); for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " "; std::cout << "\n"; } return 0; } /* 1 7 2 6 3 5 4 1 7 3 5 2 6 4 1 7 3 2 1 9 3 7 2 8 4 6 1 9 */
F
感觉这太边双了于是抄了个板子来直接草过去了。
考虑对原图求所有边双连通分量,然后按权值递增枚举所有边直到某条边的两端点在同一边双中,然后以该边的一个端点为起点 DFS 找终点为另一端点的环即可。
忘了在什么地方被这个坑过了——求环并记录方案的经典算法:DFS 钦定每个点仅能经过一次,并且回溯时将回溯的结点从环中删除。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kM = 1e6 + 10; //============================================================= int n, m; int edgenum = 1, head[kN], v[kM], ne[kM]; int dfnnum, dfn[kN], low[kN]; bool bridge[kM], vis[kN]; int dccnum; int dccid[kN]; std::vector <int> dcc[kN]; struct Edge { int u, v, w; } e[kM]; bool flag = 0; std::vector <int> ans; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Add(int u_, int v_) { v[++ edgenum] = v_; ne[edgenum] = head[u_]; head[u_] = edgenum; } void Tarjan(int u_, int from_) { dfn[u_] = low[u_] = ++ dfnnum; for (int i = head[u_]; i > 1; i = ne[i]) { int v_ = v[i]; if (!dfn[v_]) { Tarjan(v_, i); if (low[v_] > dfn[u_]) bridge[i] = bridge[i ^ 1] = 1; low[u_] = std::min(low[u_], low[v_]); } else if (i != (from_ ^ 1)) { low[u_] = std::min(low[u_], dfn[v_]); } } } void Dfs(int u_, int id_) { dccid[u_] = id_; dcc[id_].push_back(u_); for (int i = head[u_]; i > 1; i = ne[i]) { int v_ = v[i]; if (dccid[v_] || bridge[i]) continue; Dfs(v_, id_); } } bool cmp(Edge fir_, Edge sec_) { return fir_.w < sec_.w; } void Init() { n = read(), m = read(); edgenum = 1; dfnnum = 0; dccnum = 0; for (int i = 1; i <= n; ++ i) { head[i] = dfn[i] = low[i] = vis[i] = 0; dccid[i] = 0; } for (int i = 1; i <= 2 * m; ++ i) bridge[i] = 0; for (int i = 1; i <= m; ++ i) { int u_ = read(), v_ = read(), w_ = read(); if (u_ == v_) continue; e[i] = (Edge) {u_, v_, w_}; Add(u_, v_), Add(v_, u_); } for (int i = 1; i <= n; ++ i) { if (!dfn[i]) Tarjan(i, 0); } for (int i = 1; i <= n; ++ i) { if (!dccid[i]) Dfs(i, ++ dccnum); } } void Dfs2(int u_, int fa_, int end_) { vis[u_] = 1; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (flag) return ; if (v_ == end_ && v_ != fa_) { flag = 1; return ; } if (vis[v_]) continue; ans.push_back(v_); Dfs2(v_, u_, end_); if (flag) return ; ans.pop_back(); } } void Solve() { std::sort(e + 1, e + m + 1, cmp); for (int i = 1; i <= m; ++ i) { int u_ = e[i].u, v_ = e[i].v; if (dccid[u_] == dccid[v_]) { std::cout << e[i].w << " "; flag = 0; ans.clear(); ans.push_back(u_), ans.push_back(v_); vis[v_] = vis[u_] = 1; Dfs2(v_, u_, u_); std::cout << ans.size() << "\n"; for (auto x: ans) std::cout << x << " "; std::cout << "\n"; return ; } } } //============================================================= int main() { // freopen("1.txt", "r", stdin); int T = read(); while (T --) { Init(); Solve(); } return 0; } /* 1 6 6 1 2 1 2 3 1 3 1 1 4 5 1 5 6 1 6 4 1 */
G
好玩的 DP 状态。
如果只能向右边刷漆是套路二维 DP,记 表示使用前 个格子将 刷漆的最小代价,转移时考虑第 个格子是否使用即可,时空复杂度 级别。
但这题可以向左右刷漆,于是可能出现某时刻被刷漆区域并不是一段连续的前缀的情况,上述状态无法描述,于是考虑再加一维来描述上述形态。发现上述状态的实质是:向右侧刷漆时仅需关注最右侧的已被涂色的格子。类似地可以发现,需要向左侧刷漆时仅需关注最左侧的未被涂色的格子的位置,于是修改状态,记 表示使用前 个格子刷漆,使得最左侧未被涂色的格子为 ,最右侧的已被涂色的格子为 时的最小代价。
初始化 ,转移时考虑第 个格子是否使用,若使用向左还是向右:
-
若不使用,则有:
-
若向左,则可以覆盖 。若有 ,则可以全部涂色前缀 ,则此时最右侧的已涂色格子变为 ,则有转移:
- 若向右,则可以覆盖 ,则最右侧的已涂色格子变为 。此时考虑是否会对最左侧的未被涂色格子产生影响,则有:
答案即为 。
时空复杂度均为 级别,题解里说这题可以 ,感觉很牛逼。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 110; //============================================================= int n, a[kN], f[kN][kN][kN]; //============================================================= void Init() { std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; } void DP() { for (int i = 0; i <= n; ++ i) { for (int j = 0; j <= n + 1; ++ j) { for (int k = 0; k <= n; ++ k) { f[i][j][k] = n; } } } f[0][1][0] = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n + 1;++ j) { for (int k = 0; k <= n; ++ k) { f[i][j][k] = std::min(f[i][j][k], f[i - 1][j][k]); int l = std::max(i - a[i] + 1, 1); if (l <= j) { int nk = std::max(i, k); f[i][nk + 1][nk] = std::min(f[i][nk + 1][nk], f[i - 1][j][k] + 1); } int r = std::min(i + a[i] - 1, n); int nk = std::max(k, r); if (j < i) { f[i][j][nk] = std::min(f[i][j][nk], f[i - 1][j][k] + 1); } else { f[i][nk + 1][nk] = std::min(f[i][nk + 1][nk], f[i - 1][j][k] + 1); } } } } std::cout << f[n][n + 1][n] << "\n"; } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { Init(); DP(); } return 0; }
写在最后
参考:https://zhuanlan.zhihu.com/p/681725397。
学到了什么:
- E:打表!
- F:找环经典算法。
- G:有趣的状态设计。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!