CSUACM2024新生赛 - 第1场 题解
写在前面
比赛地址:https://www.luogu.com.cn/contest/210420。
鉴于出题人水平大部分是原。
A 英雄联盟世界赛 签到,模拟
首先要读懂题意,在要晋级和淘汰的关键场次中,也就是队伍有两场胜利或者两次失败,那么则需要通过三局两胜制来获得该场比赛的胜利,对于非关键场次,那么只需要一场决胜负,所以直接贪心模拟就可以了
#include<bits/stdc++.h> int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int x, y; std::cin >> x >> y; int ans[3][3] = { {4, 4, 6}, {3, 3, 4}, {2, 2, 2} }; std::cout << ans[x][y] << "\n"; return 0; }
B Cut! Increasing! 结论
首先考虑,连续的 是一定在一块的,这样我们便可以将 变成 或者
再考虑一下,一个漂亮的字符串是这样的形式的 ,所以如果在原 中有 形式,那么我们便可以将这整个切出来,剩下的 切块
我们将 缩起来后,变成 。 如果 中有 子串,那么答案就是 ,否则为 。
#include<bits/stdc++.h> void solve() { std::string s; std::cin >> s; std::vector<int> a; for (int i = 0; i < s.size() - 1; i++) { if (s[i] != s[i + 1]) a.push_back(s[i] - '0'); } a.push_back(s.back() - '0'); bool flag = false; for (int i = 0; i + 1 < a.size(); i++) { if (!a[i] && a[i + 1]) flag = true; } std::cout << a.size() - flag << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int t; std::cin >> t; while (t--) { solve(); } return 0; }
C 小予老师请客吃饭 概率期望
样例分析
当吃一串能吃饱,而将要吃第一串时,每一串都有肉,则 能取到肉,故只用 次就能吃饱,答案为 。
当吃零串能吃饱,则一串都不用取,故答案为 。
当吃两串能吃饱,则第一串一定能吃到肉;取第二串时,有一串是空的,有可能要多取一次、两次、……、甚至无穷次,有:。根据无穷级数相关理论(后续会给出推导),答案为 。
方法一
若直接利用数学期望的公式求解,想一步登天,难度较大。于是,我们的思路是,设当前已经吃了 串,要吃第 串,求本轮取串需要多少次,然后将每轮取串次数期望求和,得到总期望。
设已经吃了 串,要取 次才能吃到吃第 串,则当前取一次取到肉串的概率是:
有:
根据无穷级数:
又 ,则:
于是:
故总期望为:
方法二
设 表示已经吃了 串,吃饱还需要取串次数的数学期望。则有:
化简为:
则:
详见本视频: https://www.bilibili.com/video/BV1Ar19YyERv。于是答案为:
代码
#include<bits/stdc++.h> #define LL long long #define LD long double using namespace std; const int N = 1e6+6; LL n,m; LD pr[N]; void work(){ cin>>n>>m; LD res=n*(pr[n]-pr[n-m]); printf("%.5Lf\n",res); } void pre(){ for(LL i=1;i<=1e6;++i){ LD ad=1.0/(LD)i; pr[i]=pr[i-1]+ad; } } int main(){ LL _=1; cin>>_; pre(); while(_--){ work(); } }
D AtForces 大陆 二分答案,贪心
首先考虑到,先不杀史莱姆更优。
然后问题就转化为了原来最少有多少个史莱姆时,经过繁衍,到最后有至少 个史莱姆的问题
如果直接模拟去做,显然会超时。
我们这样考虑,如果能进行繁衍,那么可以繁衍的史莱姆数量是有个下界 的。然后每次是可以产生 个可以繁衍的史莱姆,就相当于少了 个可以繁衍的史莱姆,那么假设最开始有 个史莱姆,那么可以繁衍的次数就是 次,所以最后的总史莱姆数就是
二分找到满足 的最小的 即可
当然,这样的写法是需要特判的,如果 ,那么取 ,如果 ,取 。
#include<bits/stdc++.h> #define int long long void solve() { int m, k, h; std::cin >> m >> k >> h; if (m == k) { std::cout << std::min(m, h) << "\n"; return; } int l = -1, r = h + 1, mid; auto check = [&](int x) { int t = (x - m) / (m - k) + 1; if (x < m) t = 0; if (k * t + x >= h) return true; else return false; }; while (l + 1 != r) { mid = l + r >> 1; if (check(mid)) r = mid; else l = mid; } std::cout << r << "\n"; } signed main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int t; std::cin >> t; while (t--) { solve(); } return 0; }
E 立青连结 STL,set 启发式合并
multiset 使用入门题。
对于每一个可以相互到达的城市组成的连通块内,都使用两个 multiset 维护所有城市的编号以及权值,同时维护每个城市位于的连通块的编号。
对于操作 1,相当于将两个连通块 multiset 进行合并。如果直接枚举某一方的 multiset,并大力插入到另一集合中,显然复杂度是不对的,很容易被不断地向小 multiset 中插入较大的 multiset 的数据卡成 级别。但仅需要一个小优化,钦定每次枚举 size 较小的 multiset 中的元素,并向 size 较大的 multiset 中插入即可。这个技巧叫做启发式合并,其总时间复杂度为 级别。
为什么总时间复杂度为 ?因为钦定了每次将小的 set 合并到大的 set 中,则每次合并后的 set 大小一定不小于合并前较小的 set 的 size 的两倍。因此对于每一个 set 中的元素,其会在合并中被枚举到的次数一定不大于 次。
正确性显然,若其被枚举到了 次,考虑到每次 size 都会至少乘 2,则其所在 set 的 size 一定为 级别。则可以保证上述枚举至多进行 次,再乘上 set 的复杂度,总时间复杂度为 级别。
对于操作 2,相当于在维护权值的 multiset 修改一个权值,仅需删原权值,再新增一个权值即可。
对于操作 3,直接查询 multiset 的 size 即可。
对于操作 4,直接查询维护编号的 multiset 的最小值即可。
对于操作 5,查询维护点权值的 multiset 内大于 的最小的权值,使用 lower_bound
即可实现。
注意可能存在相同的点权值,需要使用 multiset,并且 erase 时需要特别注意写法,不能仅传入需要删除的权值,否则会把所有相同权值的数全删除。正确的写法是先进行 find 找到一个指向对应权值的迭代器,然后传入该迭代器进行删除。示例:s.erase(s.find(v));
。
总时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; //============================================================= int n, m; int a[kN], bel[kN]; std::set<int> s1[kN]; std::multiset<int> s2[kN]; //============================================================= void build(int x_, int y_) { if (bel[x_] == bel[y_]) return ; int bx = bel[x_], by = bel[y_]; if (s1[bx].size() > s1[by].size()) std::swap(bx, by); //启发式合并 for (auto node: s1[bx]) { s1[by].insert(node), bel[node] = by; } for (auto node: s2[bx]) s2[by].insert(node); s1[bx].clear(), s2[bx].clear(); } void modify(int x_, int y_) { s2[bel[x_]].erase(s2[bel[x_]].find(a[x_])); a[x_] = y_; s2[bel[x_]].insert(y_); } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); std::cin >> n >> m; for (int i = 1; i <= n; ++ i) { std::cin >> a[i]; bel[i] = i; s1[i].insert(i), s2[i].insert(a[i]); } while (m --) { std::string opt; std::cin >> opt; if (opt == "build") { int x, y; std::cin >> x >> y; build(x, y); } else if (opt == "modify") { int x, y; std::cin >> x >> y; modify(x, y); } else if (opt == "size") { int x; std::cin >> x; std::cout << s1[bel[x]].size() << "\n"; } else if (opt == "query1") { int x; std::cin >> x; std::cout << *(s1[bel[x]].begin()) << "\n"; } else { int x; std::cin >> x; auto p = s2[bel[x]].lower_bound(a[x] + 1); if (p == s2[bel[x]].end()) std::cout << -1 << "\n"; else std::cout << *p << "\n"; } } return 0; }
F hcgg最讨厌的数学题 线性 DP
首先问题可以等价于在数列中选取一些数,这些数可以组成等差数列的方案数。
初看一眼不会写,没关系,看一下数据范围, 。一看似乎可以 大力去写。
尝试一下,我们可以考虑选择两项作为等差数列中相邻的两项,那么这个等差数列的样子就确定了。
我们设出该方程 作为以 为结尾,公差为 的方案数(这其实认为这个等差数列有两个以上的数) ,那么, 就可以这样转移: ,其中 ,( 是因为可以在第 个数之前只选择第 个数)。对答案贡献就是
所以直接 转移方程即可。
在实现上,由于可能 ,我们可以对公差加上一个 ,使得 。
#include<bits/stdc++.h> using ll = long long; const int mod = 998244353; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int n; std::cin >> n; std::vector<std::vector<int>> f(n + 1, std::vector<int>(4e4 + 1, 0)); std::vector<int> a(n + 2); for (int i = 1; i <= n; i++) { std::cin >> a[i]; } ll ans = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j < i; j++) { int d = a[i] - a[j] + 2e4; f[i][d] = (f[i][d] + f[j][d] + 1) % mod; ans = (ans + f[j][d] + 1) % mod; } } ans = (ans + n) % mod; std::cout << ans << "\n"; return 0; }
G 画壁 二进制,结论
原:CF1415D
为使得该数列 不单调不降,等价于经过若干次操作后,存在某个位置大于后一个位置。称该位置 为断点。显然被操作的部分,一定仅有以断点 为右端点的的一段区间 ,加上以 为左端点的一段区间 。 的操作结果构成较大的数, 的操作结果构成较小的数。该结论正确性显然。为了保证代价最小,断点不可能存在两个及以上。
为使断点位置满足条件,需要对它前后分别操作。考虑暴力枚举断点以及左右两区间的长度求最小代价,时间复杂度 级别,无法通过本题。
再观察题目的特殊性质,从位运算的角度考虑,可以发现:若有三个连续的数的最高位相同,则可将后面两个数异或起来消去最高位,使得第一个数大于后面的数,此时仅需操作 1 次即可。又数列单调不降,且 ,则最长的、使得三个连续的数最高位不同的数列长度不大于 。
则加上特判 时直接输出 1,即可通过本题。
//知识点:结论 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long const int kN = 60 + 10; //============================================================= int n, ans = kN, a[kN]; //============================================================= 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 Chkmax(int &fir, int sec) { if (sec > fir) fir = sec; } void Chkmin(int &fir, int sec) { if (sec < fir) fir = sec; } //============================================================= int main() { n = read(); if (n > 60) { printf("1\n"); return 0; } for (int i = 1; i <= n; ++ i) a[i] = a[i - 1] ^ read(); for (int l = 1; l < n - 1; ++ l) { for (int r = l; r < n; ++ r) { for (int k = r + 1; k <= n; ++ k) { if ((a[r] ^ a[l - 1]) > (a[k] ^ a[r])) { Chkmin(ans, (r - l) + (k - r - 1)); } } } } printf("%d\n", (ans == kN) ? -1 : ans); return 0; }
写在最后
败犬女主真好看,这老八真倪玛是个后面忘了。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)