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 的影响,钦定 \(a_0 = a_{n + 1} = 0\)。
先考虑对每个位置均进行一次操作,在此基础上检查能否合并某些操作。一种显然的贪心策略是对于一段连续的需要修改的区间,操作顺序应当从外向内,操作范围应当从大到小,尽可能在一次操作中修改到区间两端目标相同的多个位置,从而节省操作次数。
则记 \(f_{l, r}(0\le l\le r\le n + 1)\) 表示将区间 \([l, r]\) 修改为目标最多能节省的操作次数,初始化 \(f_{i, i}(0\le i\le n+1) = 0\),转移时枚举区间分界并考虑两端的位置能否通过一次操作达到目标,则有转移:
共有 \(n + 2\) 个位置,考虑到钦定了 \(a_0 = a_{n + 1} = 0\) 的影响,则答案为:
时间复杂度 \(O(n^3)\) 级别。
//
/*
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
图论转化。
将 \(n\) 个工程师和 \(m\) 个问题的对应关系抽象成一张二分图 \(G = <V_1, V_2, E>\),若工程师 \(i(i\in V_1)\) 可解决问题 \(j(j\in V_2)\),在二分图中连边 \(i\rightarrow j\)。则问题实质为求得阈值 \(k\),使得对于所有大小不超过 \(k\) 的 \(V_2\) 的子集 \(S\subset V_2\),均存在从 \(V_1\) 到 \(S\) 的一个完美匹配。
考虑霍尔定理:对于二分图 \(G = <V_1, V_2, E>\),\(S\subset V_2, |S| \le |V_1|\),则 \(G\) 中存在 \(V_1\) 到 \(S\) 的完美匹配当且仅当对于任意 \(T \subset S\),均有 \(|T|\le |N(T)|\),其中 \(N(T)\) 为 \(T\) 的邻域,代表所有与 \(T\) 中结点相邻的点集,即有 \(N(T) = \{u | u\in V_1, v\in T, (u, v)\in E\}\)。
于是考虑枚举所有 \(V_2\) 的子集,枚举它们的邻域并检查是否符合上式,若不符合则标记该子集的大小。根据霍尔定理答案即为阈值 \(k\) 使得点集大小 \(i\le k\) 全部都没有被标记。
注意实现,时间复杂度为 \(O(m\times 2^m)\) 级别。
//
/*
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
对于一点线对 \((x, y, l, r)\),一个显然的想法是若 \(l\le x\le r\),则尝试作垂线段,否则最优的选择是连向 \((l, 0)\) 或 \((r, 0)\)。
考虑处理出在线段两两不相交限制下,点 \((x_i, y_i)\) 连向对应的线段 \((l_i, r_i)\) 的合法范围 \((l'_i, r'_i)\)。所有区间 \((l_i, r_i)\) 互不相交,手玩下发现这使得所有合法范围 \((l'_i, r'_i)\) 必然是一段连续的区间,或者为空集。
那么如何处理 \((l'_i, r'_i)\)?同样是根据 \((l_i, r_i)\) 互不相交,发现仅需考虑 \((x_i, y_i)\) 两侧满足 \(y_j\le y_i\) 的点 \(j\) 的影响,需要满足 \((x_i, y_i)\) 连向 \((l'_i, r'_i)\) 的线段斜率小于 \((x_i, y_i)\) 连向 \((x_j, y_j)\) 的斜率,发现寻找有影响的点的过程可以用单调栈维护。考虑正向反向枚举点线对,通过维护 \(y\) 坐标递减的单调栈即可求得所有点线对的合法区间。检查其中是否有空集,若无空集则讨论连向什么位置最优即可。
实现细节详见代码,总时间复杂度 \(O(n)\) 级别。
开 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;
}
写在最后
数据范围和时限都有点抽象呃呃

浙公网安备 33010602011771号