2915 字
15 分钟
20251026测验题解与STD

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败淘汰。

时间复杂度:O(T)\mathcal{O}(T)

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#

如果每次询问时处理 f(n)f(n),时间复杂度为 O(tn)\mathcal{O}(tn)。 考虑预处理 11nnf(n)f(n) 值,每次 O(1)\mathcal{O}(1) 询问答案。预处理时,每算出一个 f(i)f(i),就更新 maxmaxminmin 的值。总时间复杂度 O(1e6+t)\mathcal{O}(1e6+t)。 注意要开 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#

给定一个非降序整数序列 [a1,a2,,an][a_1,a_2,\ldots,a_n]。定义子序列的评分为 s1s2sdd!\frac{s_1 \cdot s_2 \cdot \ldots \cdot s_d}{d!},空序列评分为 1。序列的配置长度 = 在所有评分最大的子序列中,长度的最大值。

求:对每个前缀 [a1,a2,,ak][a_1,a_2,\ldots,a_k],输出它的配置长度。

Solution1#

我们先来看如何求一个非降序序列 s1,s2,,ss_1, s_2, \ldots, s_\ell 的配置长度。

如果我们选择一个长度为 kk 的子序列,为了获得最大评分,最优的选择就是取序列中最大的 kk 个元素。由于序列是非降序的,最大的 kk 个元素就是最后的 kk 个元素。因此,所有可能成为答案的候选子序列都是序列的后缀。

现在我们把从右往左数的第 ii 个元素除以 ii。于是这个序列变成了:

s1,  s21,  ,  s12,  s1.\frac{s_1}{\ell}, \; \frac{s_2}{\ell-1}, \; \ldots, \; \frac{s_{\ell-1}}{2}, \; \frac{s_\ell}{1}.

注意到,原序列某个后缀的评分,等于在这个新序列中对应后缀的乘积。

原序列满足

s1s2s1s.s_1 \leq s_2 \leq \ldots \leq s_{\ell-1} \leq s_\ell.

而分母部分满足

1111211.\frac{1}{\ell} \leq \frac{1}{\ell-1} \leq \ldots \leq \frac{1}{2} \leq \frac{1}{1}.

把这两个条件结合起来,可以得到:

s1s21s12s1,\frac{s_1}{\ell} \leq \frac{s_2}{\ell-1} \leq \ldots \leq \frac{s_{\ell-1}}{2} \leq \frac{s_\ell}{1},

因此,新序列依然是非降序的。

为了使新序列后缀的乘积最大,我们会选择新序列中所有 1\geq 1 的元素(这些元素必然形成一个后缀,因为新序列是非降序的)。如果新序列中有元素恰好等于 1,那么我们必须把它们也选进去,这样才能保证在最大评分的所有子序列中长度最大。

因此,序列 s1,s2,,ss_1, s_2, \ldots, s_\ell 的配置长度,就是新序列

s1,  s21,  ,  s12,  s1\frac{s_1}{\ell}, \; \frac{s_2}{\ell-1}, \; \ldots, \; \frac{s_{\ell-1}}{2}, \; \frac{s_\ell}{1}

的最长后缀长度,并且这个后缀中的每个元素都不小于 1。

接下来,我们要计算给定序列 [a1,a2,,an][a_1,a_2,\ldots,a_n] 的所有前缀的配置长度。

对某个前缀 [a1,a2,,ak][a_1,a_2,\ldots,a_k],它的配置长度就是新序列

a1k,  a2k1,  ,  ak12,  ak1\frac{a_1}{k}, \; \frac{a_2}{k-1}, \; \ldots, \; \frac{a_{k-1}}{2}, \; \frac{a_k}{1}

的最长后缀长度,其中的每个元素都不小于 1。

我们可以用二分查找来求这个长度。注意不能显式构造每个前缀对应的“变换序列”,那样会太慢。相反,在二分的每一步,我们可以直接计算该位置对应的值,判断它是否 1\geq 1。这样就能在 O(logn)\mathcal{O}(\log n) 的时间内求出每个前缀的配置长度。

时间复杂度: 每个测试用例的时间复杂度为 O(nlogn)\mathcal{O}(n \log n)

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#

对前缀 A[1..k]A[1..k] 构造的「变换序列」

A1k,  A2k1,  ,  Ak1\frac{A_1}{k},\;\frac{A_2}{k-1},\;\ldots,\;\frac{A_k}{1}

是非降序的(证明见 Solution 1)。 我们要找的是最长后缀满足其中所有元素 1\geq 1。 设该后缀长度为 dkd_k,则

Akdk+1dk1,  Akdk+2dk11,  ,  Ak11.\frac{A_{k-d_k+1}}{d_k}\geq 1,\; \frac{A_{k-d_k+2}}{d_k-1}\geq 1,\;\ldots,\; \frac{A_k}{1}\geq 1.

由于序列非降,最左边的限制最强,因此只需

Akdk+1dk.(*)A_{k-d_k+1}\geq d_k. \tag{*}

我们按 k=1..nk=1..n 顺序处理,维护当前最优长度 dd(即 dkd_k)。 当 kk 增加 1 时,先令 dd 不变,然后尝试向前扩展一位:

只要 d<kd<k 且新的「最左元素」满足

Akdd+1A_{k-d}\geq d+1

就说明能把第 kdk-d 个元素也拉进来,于是 dd+1d\leftarrow d+1。 因为序列非降,每次扩展后仍保证 ()(*) 对新的 dd 成立;否则停止扩展。

时间复杂度:O(n)\mathcal{O}(n)

Std2#

略。

D 吃馒头#

Problem tags: 二分

Prediction Difficulty:Medium-Hard

Actual Difficulty:Hard

Problem Statement#

nn 个人要一起吃掉 kk 个馒头。第 ii 个人吃一个馒头要 tit_i 秒,一次最多能连续吃 aia_i 个,然后必须休息 bib_i 秒,再重复吃。所有人同时开始吃时,吃完全部 kk 个馒头所需的最短时间。

Solution#

看到最大值最小或者最小值最大,我们就要考虑二分答案。 假设当前答案时间为 pp 秒,对于第 ii 个人,设 T=(tiai+bi)T = (t_i * a_i + b_i),在每 TT 秒可以吃掉 aia_i 个馒头,则在 (p/T)T(p / T) * T 秒内可以吃掉 (p/T)ai(p / T) * a_i 个馒头,还剩下 p%Tp \% T 秒可以吃馒头,设 res=p%Tres = p \% T,在最后的 resres 秒可以吃掉 max(res/ti,ai)max(res / t_i, a_i) ,加到当前吃馒头的数量,以此可以推出 nn 个人在 pp 秒内吃掉的馒头的总和,与 kk 比较大小写一个 check 函数即可。

Std#

#include <bits/stdc++.h>
#define ll long long
#define int ll
using 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#

给定 atk,add,n,a,b(a+b=n)atk,add,n,a,b(a+b=n),第 ii 次发生攻击时的伤害为 atk+iaddatk+i\cdot add。普通硬币反面不攻击;不可摧毁硬币无论正反都攻击。求此技能总伤害的最小值与最大值。

Solution1#

Di=atk+iaddD_i=atk+i\cdot add。 最大值:令所有 nn 次都发生攻击(把普通硬币全为正面),有:

max=natk+addn(n+1)2\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2}

最小值:仅让 bb 次不可摧毁硬币发生攻击并占据最小序号 1b1\ldots b,有 min=batk\text{min}=b\cdot atk,即输出:max=natk+addn(n+1)2\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2}min=batk\text{min}=b\cdot atk

时间复杂度:O(1)\mathcal{O}(1)

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#

按攻击发生的序号从 i=1i=1 递增累加 atk+iaddatk+i\cdot add:最大值累加 i=1ni=1\ldots n,最小值累加 i=1bi=1\ldots b

由输出量级:

max=natk+addn(n+1)2、min=batk\text{max}=n\cdot atk+add\cdot\frac{n(n+1)}{2}、\text{min}=b\cdot atk

可知,数据会爆int,但不会爆long long,所以需要使用64位long long进行存储。

时间复杂度:O(n)\mathcal{O}(n)

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#

找出每个 hchc 右边有多少 orzorz 出现的总和。

Solution#

若考虑暴力求每个 hchc 能匹配的个数,时间复杂度为 O(n2)\mathcal{O}(n^2) ,会超时。

我们可以从后往前用一个 cntcnt 计算 orzorz 的数量,每次碰到 hchc 就加一次 cntcnt,也就是此位置 hchc 能匹配的 orzorz 数量。时间复杂度 O(n)\mathcal{O}(n)

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#

给定一个数组 aa,与询问的 bb 数组拼接在一起,找出拼接后的最大公共前后缀。

Solution#

设拼接后的数组为 cc,不管是长度为多少的最大公共前后缀,我们都要从前往后遍历 cc 数组,找出最大的 ll 满足 c[l]=c[2nl+1]c[l] = c[2n-l+1],此时 ll 则为答案。 因此遍历一遍 cc 数组即可,时间复杂度 O(n)\mathcal{O}(n)

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 对本场比赛出题的帮助!

20251026测验题解与STD
https://lansganbs.cn/posts/算法竞赛/20251026测验题解与std/
作者
LANSGANBS
发布于
2025-08-30
许可协议
CC BY-NC-SA 4.0