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 的影响,钦定 a0=an+1=0

先考虑对每个位置均进行一次操作,在此基础上检查能否合并某些操作。一种显然的贪心策略是对于一段连续的需要修改的区间,操作顺序应当从外向内,操作范围应当从大到小,尽可能在一次操作中修改到区间两端目标相同的多个位置,从而节省操作次数。

则记 fl,r(0lrn+1) 表示将区间 [l,r] 修改为目标最多能节省的操作次数,初始化 fi,i(0in+1)=0,转移时枚举区间分界并考虑两端的位置能否通过一次操作达到目标,则有转移:

lm<r,fl,m+fm+1,r+[al=ar]fl,r

共有 n+2 个位置,考虑到钦定了 a0=an+1=0 的影响,则答案为:

(n+21)f0,n+1

时间复杂度 O(n3) 级别。

//
/*
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=<V1,V2,E>,若工程师 i(iV1) 可解决问题 j(jV2),在二分图中连边 ij。则问题实质为求得阈值 k,使得对于所有大小不超过 kV2 的子集 SV2,均存在从 V1S 的一个完美匹配。

考虑霍尔定理:对于二分图 G=<V1,V2,E>SV2,|S||V1|,则 G 中存在 V1S 的完美匹配当且仅当对于任意 TS,均有 |T||N(T)|,其中 N(T)T 的邻域,代表所有与 T 中结点相邻的点集,即有 N(T)={u|uV1,vT,(u,v)E}

于是考虑枚举所有 V2 的子集,枚举它们的邻域并检查是否符合上式,若不符合则标记该子集的大小。根据霍尔定理答案即为阈值 k 使得点集大小 ik 全部都没有被标记。

注意实现,时间复杂度为 O(m×2m) 级别。

//
/*
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),一个显然的想法是若 lxr,则尝试作垂线段,否则最优的选择是连向 (l,0)(r,0)

考虑处理出在线段两两不相交限制下,点 (xi,yi) 连向对应的线段 (li,ri) 的合法范围 (li,ri)。所有区间 (li,ri) 互不相交,手玩下发现这使得所有合法范围 (li,ri) 必然是一段连续的区间,或者为空集。

那么如何处理 (li,ri)?同样是根据 (li,ri) 互不相交,发现仅需考虑 (xi,yi) 两侧满足 yjyi 的点 j 的影响,需要满足 (xi,yi) 连向 (li,ri) 的线段斜率小于 (xi,yi) 连向 (xj,yj) 的斜率,发现寻找有影响的点的过程可以用单调栈维护。考虑正向反向枚举点线对,通过维护 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;
}

写在最后

数据范围和时限都有点抽象呃呃

posted @   Luckyblock  阅读(219)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示