20251026测验题解与STD
赛时公告
全场首杀:
2504020207 网络25-2 - 阳灿 E题 00:05:01
顽强拼搏奖:
2504010606 计算机25-6 - 刘誉聪 02:59:43
最快解题奖:
A: 2504010610 计算机25-6 - 何俊杰 00:31:57
B: 2504090208 计算机双学士25-2 - 闫迪 00:29:08
C: 2504020207 网络25-2 - 阳灿 02:35:07
D: 2504020207 网络25-2 - 阳灿 00:45:43
E: 2504020207 网络25-2 - 阳灿 00:05:01
F: 2504010317 计算机25-3 - 刘亭瑞 00:20:53
G: 2504090208 计算机双学士25-2 - 闫迪 01:05:51
H: 2504090208 计算机双学士25-2 - 闫迪 00:59:41
A BLG回来了
Problem tags: 贪心 排序
Prediction Difficulty:Medium
Actual Difficulty:Medium
Problem Statement
16个队伍每轮自行排列进行1对1比赛,按照战力评胜负。赛制其实就是瑞士轮赛制,相同比分的队伍在同一组别比赛,3胜晋级,3败出局。目的让第二个队伍“T1”能够淘汰。
Solution
考虑贪心,最差情况就是:
-
0-0组别最强的队伍打T1,第二强的队伍和第三强的队伍打,可以让T1和第三强的队伍掉到0-1组别。
-
0-1组别安排T1和第三强的队伍比赛,让T1进入0-2组别,第三强的队伍进入1-1组别;与此同时1-0组别让最强的队伍和第二强的队伍比赛,让第二强的队伍进入1-1组别。
-
1-1组别让第三强的队伍和第二强的队伍比赛,让第三强的队伍进入1-2组别。0-2组别让T1跟任意比他弱的队伍打,进入1-2组别。
-
最后在1-2组别,让T1和第三强的队伍打,即可让T1达成3败淘汰。
时间复杂度:。
Std
void solve() { vector<int> a(16); for (int i = 0; i < 16; i++) { cin >> a[i]; } int t1 = a[1], S = 0; for (int i = 0; i < 16; ++i) { if (a[i] > t1) { S++; } } cout << (S >= 3 ? "We are the champions." : "Faker is forever.") << endl;}B 求数
Problem tags: 递推 预处理
Prediction Difficulty:Medium-Hard
Actual Difficulty:Medium
Problem Statement
略。
Solution
如果每次询问时处理 ,时间复杂度为 。 考虑预处理 到 的 值,每次 询问答案。预处理时,每算出一个 ,就更新 和 的值。总时间复杂度 。 注意要开 long long。
Std
const int N=1e6+7;int f[N];void init(){ f[1]=1; int maxn=f[1],minn=f[1]; int sign=1; for(int i=2;i<N;i++) { f[i]=f[i-1]+sign*(maxn-minn)+i; sign=-sign; // 正负互换 maxn=max(maxn,f[i]); minn=min(minn,f[i]); }}int n;void solve(){ cin>>n; cout<<f[n]<<endl;}signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); init(); int T=1; cin>>T; while(T--){solve();} return 0;}C 刀剑神域:艾恩葛朗特的评分机关
Problem tags: 二分 双指针 贪心
Prediction Difficulty:Hard
Actual Difficulty:Hard
Problem Statement
给定一个非降序整数序列 。定义子序列的评分为 ,空序列评分为 1。序列的配置长度 = 在所有评分最大的子序列中,长度的最大值。
求:对每个前缀 ,输出它的配置长度。
Solution1
我们先来看如何求一个非降序序列 的配置长度。
如果我们选择一个长度为 的子序列,为了获得最大评分,最优的选择就是取序列中最大的 个元素。由于序列是非降序的,最大的 个元素就是最后的 个元素。因此,所有可能成为答案的候选子序列都是序列的后缀。
现在我们把从右往左数的第 个元素除以 。于是这个序列变成了:
注意到,原序列某个后缀的评分,等于在这个新序列中对应后缀的乘积。
原序列满足
而分母部分满足
把这两个条件结合起来,可以得到:
因此,新序列依然是非降序的。
为了使新序列后缀的乘积最大,我们会选择新序列中所有 的元素(这些元素必然形成一个后缀,因为新序列是非降序的)。如果新序列中有元素恰好等于 1,那么我们必须把它们也选进去,这样才能保证在最大评分的所有子序列中长度最大。
因此,序列 的配置长度,就是新序列
的最长后缀长度,并且这个后缀中的每个元素都不小于 1。
接下来,我们要计算给定序列 的所有前缀的配置长度。
对某个前缀 ,它的配置长度就是新序列
的最长后缀长度,其中的每个元素都不小于 1。
我们可以用二分查找来求这个长度。注意不能显式构造每个前缀对应的“变换序列”,那样会太慢。相反,在二分的每一步,我们可以直接计算该位置对应的值,判断它是否 。这样就能在 的时间内求出每个前缀的配置长度。
时间复杂度: 每个测试用例的时间复杂度为 。
Std1
void slu() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> res; for (int i = 0; i < n; i++) { int l = 1, r = i + 1; while (l <= r) { int m = (l + r) / 2; if (a[i - m + 1] >= m) { l = m + 1; } else { r = m - 1; } } res.push_back(r); } for (auto &x : res) { cout << x << ' '; } cout << endl;}Solution2
对前缀 构造的「变换序列」
是非降序的(证明见 Solution 1)。 我们要找的是最长后缀满足其中所有元素 。 设该后缀长度为 ,则
由于序列非降,最左边的限制最强,因此只需
我们按 顺序处理,维护当前最优长度 (即 )。 当 增加 1 时,先令 不变,然后尝试向前扩展一位:
只要 且新的「最左元素」满足
就说明能把第 个元素也拉进来,于是 。 因为序列非降,每次扩展后仍保证 对新的 成立;否则停止扩展。
时间复杂度:。
Std2
略。
D 吃馒头
Problem tags: 二分
Prediction Difficulty:Medium-Hard
Actual Difficulty:Hard
Problem Statement
有 个人要一起吃掉 个馒头。第 个人吃一个馒头要 秒,一次最多能连续吃 个,然后必须休息 秒,再重复吃。所有人同时开始吃时,吃完全部 个馒头所需的最短时间。
Solution
看到最大值最小或者最小值最大,我们就要考虑二分答案。 假设当前答案时间为 秒,对于第 个人,设 ,在每 秒可以吃掉 个馒头,则在 秒内可以吃掉 个馒头,还剩下 秒可以吃馒头,设 ,在最后的 秒可以吃掉 ,加到当前吃馒头的数量,以此可以推出 个人在 秒内吃掉的馒头的总和,与 比较大小写一个 check 函数即可。
Std
#include <bits/stdc++.h>#define ll long long#define int llusing namespace std;
const int N = 2e5 + 7;
int n, k;int t[N], a[N], b[N];
bool check(int x){ int ans = 0; for (int i = 1; i <= n; i++) { int T = (a[i] * t[i] + b[i]); ans += (x / T) * a[i]; int y = x % T; if (a[i] * t[i] >= y) { ans += y / t[i]; } else ans += a[i]; if (ans >= k) return 1; } return 0;}
signed main(){ ios ::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int TT = 1; cin >> TT; while (TT--) { cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> t[i]; } for (int i = 1; i <= n; i++) { cin >> a[i]; } for (int i = 1; i <= n; i++) { cin >> b[i]; } int l = 0, r = 1e18 + 7; while (l < r) { int mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid + 1; } cout << l << '\n'; } return 0;}E 计算器
Problem tags: 分支结构
Prediction Difficulty:Easy
Actual Difficulty:Easy
Problem Statement
模拟两个数字的五种运算。
Solution
模拟即可。注意乘法会爆 int,需要 long long 来进行存储。
Std
int main(){ ios :: sync_with_stdio(0), cin.tie(0), cout.tie(0); int TT; cin >> TT; while (TT--) { long long a, b; char op; cin >> a >> b >> op; if ((op == '%' || op == '/') && (b == 0)) { cout << "No" << '\n'; continue; } if (op == '+') { cout << a + b << '\n'; } else if (op == '-') { cout << a - b << '\n'; } else if (op == '*') { cout << a * b << '\n'; } else if (op == '/') { cout << a / b << '\n'; } else { cout << a % b << '\n'; } }}F 边狱巴士是一款大概率发生小概率事件的游戏
Problem tags: 顺序结构 数学 贪心
Prediction Difficulty:Easy-Medium
Actual Difficulty:Easy-Medium
Problem Statement
给定 ,第 次发生攻击时的伤害为 。普通硬币反面不攻击;不可摧毁硬币无论正反都攻击。求此技能总伤害的最小值与最大值。
Solution1
记 。 最大值:令所有 次都发生攻击(把普通硬币全为正面),有:
最小值:仅让 次不可摧毁硬币发生攻击并占据最小序号 ,有 ,即输出:、
时间复杂度:。
Std1
void slu() { int atk, add, n, a, b; cin >> atk >> add >> n >> a >> b; int mn = b * atk; int mx = n * atk + add * (n * (n + 1) / 2); cout << mn << " " << mx << endl;}Solution2
按攻击发生的序号从 递增累加 :最大值累加 ,最小值累加 。
由输出量级:
可知,数据会爆int,但不会爆long long,所以需要使用64位long long进行存储。
时间复杂度:。
Std2
void slu() { int atk, add, n, a, b; cin >> atk >> add >> n >> a >> b;
{ int w = atk, ans = 0; for (int i = 0; i < b; i++) { ans += w; } cout << ans << ' '; }
{ int w = atk, ans = 0; for (int i = 0; i < n; i++) { w += add; ans += w; } cout << ans << endl; }}G hc_orz
Problem tags: 后缀和
Prediction Difficulty:Medium
Actual Difficulty:Medium-Hard
Problem Statement
找出每个 右边有多少 出现的总和。
Solution
若考虑暴力求每个 能匹配的个数,时间复杂度为 ,会超时。
我们可以从后往前用一个 计算 的数量,每次碰到 就加一次 ,也就是此位置 能匹配的 数量。时间复杂度 。
Std
const int N=1e6+7;int n;string s;int a[N],m; // 用于记录 hc和 orz的位置int suf; // 记录后缀的 orz数量void solve(){ cin>>n; cin>>s; s=' '+s; // 保证字符串从 1开始遍历 suf=m=0; for(int i=1;i<=n;i++) { if(i+1<=n&&s[i]=='h'&&s[i+1]=='c') a[++m]=i; if(i+2<=n&&s[i]=='o'&&s[i+1]=='r'&&s[i+2]=='z') a[++m]=i; }
int ans=0; for(int i=m;i>=1;i--) { if(s[a[i]]=='o') suf++; else ans+=suf; } cout<<ans<<endl;}signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int T=1; cin>>T; while(T--){solve();} return 0;}H pre and suf
Problem tags: 暴力枚举
Prediction Difficulty:Easy-Medium
Actual Difficulty:Medium-Hard
Problem Statement
给定一个数组 ,与询问的 数组拼接在一起,找出拼接后的最大公共前后缀。
Solution
设拼接后的数组为 ,不管是长度为多少的最大公共前后缀,我们都要从前往后遍历 数组,找出最大的 满足 ,此时 则为答案。 因此遍历一遍 数组即可,时间复杂度 。
Std
const int N=1e6+7;int n,q;int a[N];void solve(){ cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i];
while(q--) { for(int i=n+1;i<=2*n;i++) cin>>a[i];
int l=0; while(a[l+1]==a[2*n-l]&&l+1<=2*n) l++; cout<<l<<endl; }}signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int T=1; cin>>T; while(T--){solve();} return 0;}致谢名单
-
Author:
Chenzq7, huangce, LANSGANBS
-
Tester:
DKXTL, lry666, skycheng, suroooo, yihang_01
NOTE排名按照字典序排序。
非常感谢各位 Tester 对本场比赛出题的帮助!