The 2024 Hunan Multi-School Programming Training Contest, Round 3 部分题解
写在前面
部分简略题解,以下按个人难度向排序。
L
纯签到。
复制复制// /* 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 n; std::cin >> n; int flag = 1; for (int i = 1; i <= 3; ++ i) { int flag7 = 0; for (int j = 1; j <= n; ++ j) { int x; std::cin >> x; if (x == 7) flag7 = 1; } flag &= flag7; } std::cout << (flag ? "777\n" : "0\n"); return 0; }
K
贪心。
顺序枚举题目的过程中能做就做。
因为每道题的贡献均为 1 则贪心策略不会更劣。
// /* 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 n, s; std::cin >> n >> s; for (int i = 1; i <= n; ++ i) { int l, r; std::cin >> l >> r; if (l <= s && s <= r) ++ s; } std::cout << s << "\n"; return 0; }
F
模拟。
每页独立,于是讨论每页哪种策略更优:
- 全删除,再单选。
- 全选,再单个删除。
- 单个删除,单个选择。
然后根据需要修改的页码范围和当前页码讨论如何换页即可。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e3 + 10; const int kInf = 1e9 + 2077; //============================================================= int n, m, s, p, q; int pagenum, ans; int left = kInf, right = -kInf; bool yes1[kN], yes2[kN]; int cnt1[kN], cnt2[kN]; //============================================================= int calc(int page_) { int ret = 0, sz = ((page_ == pagenum && (n % m)) ? n % m : m); int l = (page_ - 1) * m + 1, r = l + sz - 1; for (int i = l; i <= r; ++ i) { ret += (yes1[i] ^ yes2[i]); } ret = std::min(ret, 1 + cnt2[page_]); ret = std::min(ret, 1 + sz - cnt2[page_]); return ret; } int calc2() { if (left == kInf && right == -kInf) return 0; if (s <= left) ans += right - s; else if (s >= right) ans += s - left; else ans += std::min(s - left, right - s) + right - left; } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); std::cin >> n >> m >> s >> p >> q; pagenum = ceil(1.0 * n / m); for (int i = 1; i <= p; ++ i) { int x; std::cin >> x; yes1[x] = true; cnt1[(x - 1) / m + 1] ++; } for (int i = 1; i <= q; ++ i) { int x; std::cin >> x; yes2[x] = true; cnt2[(x - 1) / m + 1] ++; } for (int i = 1; i <= pagenum; ++ i) { int ret = calc(i); if (ret) left = std::min(left, i), right = std::max(right, i); ans += ret; } std::cout << calc2() << "\n"; return 0; }
I
大力区间 DP,以下为写得很简洁的 std 做法。
考虑到初始全为 0 的影响,钦定 。
先考虑对每个位置均进行一次操作,在此基础上检查能否合并某些操作。一种显然的贪心策略是对于一段连续的需要修改的区间,操作顺序应当从外向内,操作范围应当从大到小,尽可能在一次操作中修改到区间两端目标相同的多个位置,从而节省操作次数。
则记 表示将区间 修改为目标最多能节省的操作次数,初始化 ,转移时枚举区间分界并考虑两端的位置能否通过一次操作达到目标,则有转移:
共有 个位置,考虑到钦定了 的影响,则答案为:
时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 810; //============================================================= int n, a[kN], f[kN][kN]; //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; for (int len = 2; len <= n + 2; ++ len) { for (int l = 0, r = len - 1 ; r <= n + 1; ++ l, ++ r) { for (int m = l; m < r; ++ m) { f[l][r] = std::max(f[l][r], f[l][m] + f[m + 1][r] + (a[l] == a[r])); } } } std::cout << (n + 2) - f[0][n + 1] - 1; return 0; }
H
图论转化。
将 个工程师和 个问题的对应关系抽象成一张二分图 ,若工程师 可解决问题 ,在二分图中连边 。则问题实质为求得阈值 ,使得对于所有大小不超过 的 的子集 ,均存在从 到 的一个完美匹配。
考虑霍尔定理:对于二分图 ,,则 中存在 到 的完美匹配当且仅当对于任意 ,均有 ,其中 为 的邻域,代表所有与 中结点相邻的点集,即有 。
于是考虑枚举所有 的子集,枚举它们的邻域并检查是否符合上式,若不符合则标记该子集的大小。根据霍尔定理答案即为阈值 使得点集大小 全部都没有被标记。
注意实现,时间复杂度为 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 3e4 + 10; const int kM = 20 + 10; const int kS = 2e6 + 10; //============================================================= int n, m, all, ans; std::string t; bool no[kM]; std::vector <int> v[kM]; int cnt, now_time, tim[kN], vis[kN]; //============================================================= void add(int x_) { if (tim[x_] != now_time) vis[x_] = 0, tim[x_] = now_time; if (vis[x_]) return ; vis[x_] = true; ++ cnt; } void clear() { cnt = 0; ++ now_time; } bool check_graph(int s_, int sz_) { clear(); for (int i = 0; i < m; ++ i) { if (cnt >= sz_) break; if (s_ >> i & 1) { for (auto x: v[i]) { add(x); if (cnt >= sz_) break; } } } return cnt >= sz_; } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); std::cin >> n >> m; all = (1 << m) - 1; for (int i = 1; i <= n; ++ i) { std::cin >> t; for (int j = 0; j < m; ++ j) { if (t[j] == '1') v[j].push_back(i); } } ans = m; for (int s = 1; s <= all; ++ s) { int sz = __builtin_popcount(s); if (!check_graph(s, sz)) { ans = std::min(ans, sz - 1); } } std::cout << ans << "\n"; return 0; } /* 4 6 001101 111001 001110 100100 */
J
对于一点线对 ,一个显然的想法是若 ,则尝试作垂线段,否则最优的选择是连向 或 。
考虑处理出在线段两两不相交限制下,点 连向对应的线段 的合法范围 。所有区间 互不相交,手玩下发现这使得所有合法范围 必然是一段连续的区间,或者为空集。
那么如何处理 ?同样是根据 互不相交,发现仅需考虑 两侧满足 的点 的影响,需要满足 连向 的线段斜率小于 连向 的斜率,发现寻找有影响的点的过程可以用单调栈维护。考虑正向反向枚举点线对,通过维护 坐标递减的单调栈即可求得所有点线对的合法区间。检查其中是否有空集,若无空集则讨论连向什么位置最优即可。
实现细节详见代码,总时间复杂度 级别。
开 8s 大概是因为 std 常数挺大的,在 cf 神机上都要跑 2s。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; //============================================================= struct PtLin { double x, y, l, r; } ptl[kN]; int n; //============================================================= double dis(double x1_, double y1_, double x2_, double y2_) { return sqrt((x1_ - x2_) * (x1_ - x2_) + (y1_ - y2_) * (y1_ - y2_)); } void Init() { std::stack <PtLin> st; for (int i = 1; i <= n; ++ i) { while (!st.empty() && st.top().y <= ptl[i].y) { PtLin t = st.top(); st.pop(); if (t.y < ptl[i].y) { ptl[i].l = std::max(ptl[i].l, ptl[i].x - ptl[i].y * (ptl[i].x - t.x) / (ptl[i].y - t.y)); } else if (ptl[i].x < t.x) { std::cout << -1 << "\n"; exit(0); } } st.push(ptl[i]); } while (!st.empty()) st.pop(); for (int i = n; i; -- i) { while (!st.empty() && st.top().y <= ptl[i].y) { PtLin t = st.top(); st.pop(); if (t.y < ptl[i].y) { ptl[i].r = std::min(ptl[i].r, ptl[i].x - ptl[i].y * (ptl[i].x - t.x) / (ptl[i].y - t.y)); } else if (t.x < ptl[i].x) { std::cout << -1 << "\n"; exit(0); } } st.push(ptl[i]); } } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); std::cin >> n; for (int i = 1; i <= n; ++ i) { double x, y, l, r; std::cin >> x >> y >> l >> r; ptl[i] = (PtLin) {x, y, l, r}; } Init(); double ans = 0; for (int i = 1; i <= n; ++ i) { if (ptl[i].l > ptl[i].r) { std::cout << -1 << "\n"; exit(0); } if (ptl[i].l <= ptl[i].x && ptl[i].x <= ptl[i].r) { ans += ptl[i].y; } else { ans += std::min(dis(ptl[i].x, ptl[i].y, ptl[i].l, 0), dis(ptl[i].x, ptl[i].y, ptl[i].r, 0)); } } std::cout << std::fixed << std::setprecision(15) << ans << "\n"; return 0; }
写在最后
数据范围和时限都有点抽象呃呃
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效