CSUACM2024新生赛 - 第3场 题解
写在前面
比赛地址:https://www.luogu.com.cn/contest/217790
A 袋鼠的迷宫
对于所有相邻的两列,如果两行都存在障碍物,则无法继续前进,此时判断为无法到达即可。
复制复制#include <iostream> #include <string> using namespace std; const int N = 1e6 + 5; const string ans[2] = {"NO", "YES"}; int n; bool vis[2][N]; int main() { cin >> n; for(bool j : {0, 1}) for (int i = 1; i <= n; i++) { char c; cin >> c; vis[j][i] = (c == '.'); } bool flag = 1; for (int i = 2; i <= n; i++) flag &= ((vis[0][i - 1] && vis[0][i]) || (vis[1][i - 1] && vis[1][i])); cout << ans[flag] << endl; return 0; }
B 袋鼠大元帅
首先发现我们可以把所有袋鼠用 次操作聚集到任一个角。所以我们选择距 最近的角,讨论 的奇偶,可发现最多还需 次操作。
#include <iostream> using namespace std; int main() { int n, a, b; cin >> n >> a >> b; if(a*2 <= n) { for (int i = 1; i < n; i++) cout << 'U'; for (int i = 1; i < a; i++) cout << 'D'; } else { for (int i = 1; i < n; i++) cout << 'D'; for (int i = n; i > a; i--) cout << 'U'; } if(b*2 <= n) { for (int i = 1; i < n; i++) cout << 'L'; for (int i = 1; i < b; i++) cout << 'R'; } else { for (int i = 1; i < n; i++) cout << 'R'; for (int i = n; i > b; i--) cout << 'L'; } return 0; }
C 袋鼠排队
首先考虑暴力模拟,依次向每个序列末尾添加,如果遇到 就表示当前这个序列已经填完,之后再填到这个序列就跳过。
考虑复杂度,若某个序列特别长,在其他序列都填完后每次再填入一个数需要遍历每个序列,复杂度 。
但注意到每次遍历已填完的序列很浪费,所以我们使用链表,一个序列填完后就把他删除,复杂度 。
需使用vector存储每个序列,防止mle。
#include <iostream> #include <vector> using namespace std; const int N = 3e5 + 5; int n, m, b[N], nxt[N], lst[N]; vector<int> a[N]; int main() { ios::sync_with_stdio(0); cin.tie(nullptr); cin >> m; for (int i = 1; i <= m; i++) { cin >> b[i]; n += (b[i] == -1); } for (int i = 1; i <= n; i++) nxt[i] = i % n + 1, lst[i] = i - 1; lst[1] = n; for (int i = 1, j = 1; i <= m; i++, j = nxt[j]) { if(b[i] != -1) a[j].push_back(b[i]); else lst[nxt[j]] = lst[j], nxt[lst[j]] = nxt[j]; } cout << n << endl; for (int i = 1; i <= n; i++) { cout << a[i].size() << ' '; for(int j = 0; j < a[i].size(); j++) cout << a[i][j] << ' '; cout << endl; } return 0; }
D 袋鼠总统
二分答案加树形 dp。
答案显然具有单调性(若某个 能满足预算,则比他大的 都能满足预算),考虑二分答案。
考虑如何检查某个 是否合法。设 表示若要满足 子树内的预算至少还需要从祖先那里获得多少补助,所以转移时有:
复杂度 。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5 + 5, A = 1e9; int n, a[N], head[N], nxt[N], to[N], edge_cnt; inline void add_edge(int u, int v) { nxt[++edge_cnt] = head[u]; head[u] = edge_cnt; to[edge_cnt] = v; } ll dfs(int u, int x) { ll res = a[u] - x; for (int i = head[u]; i; i = nxt[i]) res += dfs(to[i], x); return max(res, 0ll); } int main() { cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for(int i = 2; i <= n; i++) { int p; cin >> p; add_edge(p, i); } int l = 0, r = A; while(l < r) { int mid = (l + r) / 2; if(dfs(1, mid)) l = mid + 1; else r = mid; } cout << l << endl; return 0; }
E 袋鼠喝奶茶
贪心,细节题
因为每次都会删去整段前缀,所以可以把连续的 01 子串看为一段。显然一次操作减少的段数只能为 1 或 2,则一种显然的贪心策略是,尽可能每次操作仅减少一段。
设段长为 ,则最优情况下,每次操作应当选择 的、最靠左的段删除,并删除其中一个字符,即可保证只会减少一段;若没有 的段,则每次删除就会减少两端,但是由于只进行一步操作也算一次,所以要记得上取整。
思路比较好想,细节可以看代码。
#include <iostream> #include <vector> using namespace std; void solve() { int n; cin >> n; string s; cin >> s; s = '1' + s; vector<int> cnt(n + 1, 0); //统计每一段的长度 int nw = 0; //统计总段数 for (int i = 1; i <= n; ) { char ch = s[i]; int j = i + 1; while(j <= n && s[j] == ch) j++; cnt[++nw] = j - i; i = j; } int j = 1, x = -1; //删除操作具有单调性,用j表示删到哪里,x判断有多少不可删 for (int i = 1; i <= nw; i++) { if (j < i) j = i; //只能删除当前段或者后面的 while (j <= nw && cnt[j] <= 1) j++; if (j >= nw + 1) {x = i - 1; break;} //没有可以删的就break直接计算 cnt[j]--; } if (x != -1) cout << x + (nw - x + 1) / 2 << endl; else cout << nw << endl; //最多也只能删分段的次数 } int main() { int t; cin >> t; while (t--) solve(); return 0; }
F 袋鼠逛商场1
gxd出的,灵感来源是ICPC Taichung Regional 的 H, 出了就没指望有人做出来(笑)
若不限制初始位于第一层,则本题为:限制条件为连续的 +-
不能超过 个,求构造长为 的序列的方案数。显然此时 +-
是等价的,则考虑用 0 表示操作 0,1 表示操作 +-
。考虑 DP,设 表示,只考虑前 个位置,第 个位置放操作 时的合法方案数,则:
- 0 没有限制可以直接转移:
- 为了使当前位置合法,连续 1 不超过 个,可以枚举非 1 的断点 求和,并通过前缀和 实现 转移:
注意特判处理 的情况。
最后处理从第一层开始的限制,把序列翻转过来考虑,题意变为从任意层开始,最后必须落在第一层,因为第一层不能下行所以答案为
第一维可以滚动优化。这时发现维护前缀和即可, 设 的前缀和为 ,可进一步优化转移。又发现 +-
等价,代码中 表示的就是填 +-
的个数,所以转移时 。
#include <iostream> using namespace std; const int N = 1e6 + 5, mod = 998244353; int n, k, s[N]; inline int add(int x, int y) { int res = x + y; return res >= mod ? res - mod : res; } inline int sub(int x, int y) { int res = x - y; return res < 0 ? res + mod : res; } int main() { cin >> n >> k; int f[2] = {1, 0}; s[0] = 1; for (int i = 1; i <= n; i++) { f[0] = add(f[0], 2*f[1]%mod); f[1] = sub(s[i - 1], i - k < 0 ? 0 : s[i - k]); s[i] = add(s[i - 1], add(f[0], f[1])); } cout << add(f[0], f[1]) << endl; return 0; }
G 袋鼠逛商场2
DP, 乘法逆元
请先阅读前一题题解。
和前一题的区别在于0失去了断点功能,而+-互为断点
dp转移式变为:
枚举 个数中的 +-
总个数,用 0 填充剩余部分,则每个方案对于答案的贡献为 。
特殊限制等处理方式同前。
#include <iostream> using namespace std; const int N = 1e6 + 5, mod = 998244353; int n, k, s[N]; inline int add(int x, int y) { int res = x + y; return res >= mod ? res - mod : res; } inline int sub(int x, int y) { int res = x - y; return res < 0 ? res + mod : res; } inline int mul(int x, int y) { return 1ll * x * y % mod; } int qpow(int x, int y) { int res = 1; while(y) { if(y&1) res = mul(res, x); x = mul(x, x); y >>= 1; } return res; } int main() { cin >> n >> k; s[0] = 1; int res = 1; for (int i = 1, c = 1; i <= n; i++) { int f = sub(s[i - 1], i - k < 0 ? 0 : s[i - k]); c = mul(c, mul(qpow(i, mod - 2), n + 1 - i)); res = add(res, mul(f, c)); s[i] = add(s[i - 1], f); } cout << res << endl; return 0; }
写在最后
如果澳大利亚的袋鼠集结入侵乌拉圭,那么最后获得胜利的是哪一方? - 知乎:
https://www.zhihu.com/question/320455456。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】