专栏文章
题解:T580827 欧冠附加赛
题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miom6hl4
- 此快照首次捕获于
- 2025/12/02 21:28 3 个月前
- 此快照最后确认于
- 2025/12/02 21:28 3 个月前
知识点
本题考察前缀和与单调队列。
知识补充
OI WiKi 前缀和讲解。
OI WiKi 单调队列讲解。
P1886 滑动窗口 /【模板】单调队列;
P1886 滑动窗口 /【模板】单调队列 题解。
单调队列 博客 ;单调队列 博客 ;
单调队列 博客 ;单调队列 博客 。
OI WiKi 单调队列讲解。
P1886 滑动窗口 /【模板】单调队列;
P1886 滑动窗口 /【模板】单调队列 题解。
单调队列 博客 ;单调队列 博客 ;
单调队列 博客 ;单调队列 博客 。
简化版题意
可以先求出曼城的每分钟的净胜球(“净胜球”知识科普)。
将题目转化为:扣除长度不超过 的一段区间内的曼城的全部净胜球,要求这个区间内曼城净胜球最小。因为曼城总净胜球固定不变,所以此时剩余的曼城净胜球最大。
注:如果所有符合要求的区间内曼城的净胜球都是正数,就不需要扣除任何区间,因为扣除后曼城总净胜球反而会减少。
形式化题意
由上述简化版题意,可以进一步得到形式化题意。
给定序列:
规定序列:
设 为 到 求和。
当以下式子取到最小值时(此处使用 方便后续计算),
当以下式子取到最小值时(此处使用 方便后续计算),
如果 ,求以下两个式子的最大值( 可能有多个最小值,会对应不同的 )。
注:经计算,这两个式子的差是 ,在 确定时为定值,所以保证两式能同时取到最大值。
特别地,如果 ,直接求 和 。
前缀和部分
观察上方的简化版题意和形式化题意,可以发现出现了多次“求和”,其中主要是求 序列的区间和。
如果直接使用循环遍历求区间和,总代码时间复杂度可能达到 ,最多获得 分。
如果使用前缀和 :
- ;
- ;
- ;
- 。
预处理求得 序列的所有值,并在接下来 时间复杂度内求出所有区间和。
经过前缀和计算,我们可以将之前的求和式全部转化为前缀和数列中的某个数。具体对应如下:
-
;
-
;
-
;
-
。
注:此处要特别注意, 而非 。这个式子与前缀和求区间和相关知识有关。
前缀和(预处理)代码
CPP// 进球前缀和
for(int i=0; i<n; i++)
M[i+1] = M[i] + (s[i] == 'M'),
R[i+1] = R[i] + (s[i] == 'R');
// 净胜球前缀和
for(int i=1; i<=n; i++)
a[i] = M[i] - R[i];
单调队列部分
求和问题解决后,我们迎来了第二大问题:求所有 的最小值。
如果遍历所有符合条件的 ,时间复杂度达到 ,超时。
这时候就要用到单调队列了。
根据例题 P1886,我们可以了解到:单调队列可以在 时间复杂度内,求出数列中各个长度为 的区间的最小值(或最大值)。
知识点转化
只求到长度为 的区间的最小值还不够,还有一步。
我们求的是 的最小值。为了使 最大,可以对于所有 ,都求出 的最大值,此时原式 取到最小。
单调队列·实现方法
遍历 数列得到每个 ,并同时求以 为结尾的长度最大为 的区间内的最大值,作为 。
即 (此数列长度恰好为 )。
即 (此数列长度恰好为 )。
注:整数 的取值区间的长度是 而非 。
根据 的式子,我们实际上要扣除 这个区间, 的取值区间的长度是 ,且以 结尾。
根据 的式子,我们实际上要扣除 这个区间, 的取值区间的长度是 ,且以 结尾。
使用区间长度计算公式(区间长度 右边界 左边界 )写成:。
解得:。
解得:。
使用双端队列 ,
q.front() 记录 。移除出界元素
由前文,因为 ,所以,当 时,我们就认定它“出界”,删除这个元素。
注 1:所有的 都从各个 之前的双端队列中获得,而 从小到大考虑。不会出现 的情况,故不考虑。
注 2:在 for 循环中,每次变量 自增 ,与之对应的 最多也就只会有 个出界,所以写 if 即可,不用写 while。
注 2:在 for 循环中,每次变量 自增 ,与之对应的 最多也就只会有 个出界,所以写 if 即可,不用写 while。
于是我们就可以写:
CPP// 1.出队·移除出界元素
if(!q.empty() && left<right-K)
q.pop_front();
维护单调递增
直接按照模板写。
CPP// 2.出队·维护单调递减
while(!q.empty() && A[i] >= A[q.back()])
q.pop_back();
新元素加入
C// 3.入队·新元素加入
q.push_back(i);
区间求和·记录最大值
对于每个 ,使用单调队列求得长度为 的区间内最大的 之后,求得到了当前 的最小取值。
即:。
即:。
用 记录 (净胜球)的最小值,如果 ,就更新 为 ,最后求出扣除的最小的曼城净胜球 。
如果这个最终值 ,说明扣除的曼城净胜球最少也是正数,也就是说整场比赛对曼城都是有利的,不需要扣除任何区间,直接输出 作为答案。
进球最大值
求出最优净胜球还没有结束。还有最后一步:在曼城净胜球最大的情况下,最大化曼城进球(也即最大化皇马进球)。
转变思路:最大化两队进球,即在 为最小值时,最小化两队扣除的进球。
也可以写成:在 为最小值时,最小化 和 。
也可以写成:在 为最小值时,最小化 和 。
更新这两个值需要分两种情况讨论:
- 如果当前的 ,此时新的 一定更优。先考虑求 的最小值,所以直接同步更新;
CPP
Mgoal = M[right] - M[left]; Rgoal = R[right] - R[left]; - 如果当前的 ,此时 已经取到最小值,新的 所对应的 可以考虑。其可能更优,所以用 函数或比较大小求出更小值(即更优值)。
CPP
或写成: CPPMgoal = min(M[right]-M[left], Mgoal); Rgoal = min(R[right]-R[left], Rgoal);if(M[right]-M[left] < Mgoal){ Mgoal = M[right] - M[left]; Rgoal = R[right] - R[left]; }
特判
最后不要忘记有趣的 测试点呀!
通过使用 DeepSeek,豆包,百度体育等进行搜索,可以找到 这场欧冠 决赛附加赛(其实题目里也有)。
他的全名是:Erling Braut Haaland。
注:
注:
Erling Haaland 是错误答案,因为题目要求写出全名。注意:虽然测试点 的第一行字符串都不含空格,但是测试点 的第一行是
CPPWho is H?,存在空格,所以要注意读入处理。有以下两种写法。string s; getline(cin, s); //读入整行
if(s == "Who is H?"){
cout << "Erling Braut Haaland";
return 0;
}
CPPstring s; cin >> s; // 读到空格就停止
// 此时 s = "Who"
if(s == "Who"){
cout << "Erling Braut Haaland";
return 0;
}
AC 代码
CPP#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 1e6+5;
int M[MAX_N], R[MAX_N], A[MAX_N];
int main(){
string s; getline(cin, s);
// 特判1
if(s == "Who is H?"){
cout << "Erling Braut Haaland";
return 0;
}
int n = s.size();
int k; cin >> k;
// 进球前缀和
for(int i=0; i<n; i++)
M[i+1] = M[i] + (s[i] == 'M'),
R[i+1] = R[i] + (s[i] == 'R');
// 特判2
if(k == 0){
cout << M[n] << ':' << R[n];
return 0;
}
// 净胜球前缀和
for(int i=1; i<=n; i++)
A[i] = M[i] - R[i];
deque <int> q;
int mini = INT_MAX,
Mgoal = 0, Rgoal = 0,
left, right;
for(int i=1; i<=n; i++){
// 单调队列操作
// 1.出队·移除出界元素
if(!q.empty() && q.front() < i-k)
q.pop_front();
// 2.出队·维护单调递减
while(!q.empty() && A[i] >= A[q.back()])
q.pop_back();
// 3.入队·新元素加入
q.push_back(i);
// 避免空队列
if(q.empty()) continue;
int l = q.front(), r = i; // 扣除区间[l+1,r] (求和)
// 净胜球前缀和区间
int dif = A[r] - A[l],
mg = M[r] - M[l], rg = R[r] - R[l];
// 更新答案
if(dif < mini) // 区间内曼城净胜球更少, 扣除
mini = dif,
Mgoal = mg, Rgoal = rg,
left = l, right = r;
else if(dif == mini)
Mgoal = min(mg, Mgoal),
Rgoal = min(rg, Rgoal),
left = l, right = r;
// 净胜球相同情况下, 扣除的进球越少越好(进球多)
}
int Msum = 0, Rsum = 0;
// 如果扣除的曼城净胜球是负数
if(mini < 0)
Msum = Mgoal,
Rsum = Rgoal;
// 扣除
int Mans = M[n] - Msum,
Rans = R[n] - Rsum;
cout << Mans << ':' << Rans;
}
彩蛋
这道题还有另外一种解法,稍做介绍。
其实这种方法与上文中的正解类似。
其实这种方法与上文中的正解类似。
在新解法中,我们从皇马净胜球角度出发,转化题意:
通过前缀和、单调队列,求出长度不超过 的区间内皇马的净胜球之和的最大值,并将这个区间扣除。同理,此时剩余的部分皇马净胜球最小,也即曼城净胜球最大。再最大化进球数。
通过前缀和、单调队列,求出长度不超过 的区间内皇马的净胜球之和的最大值,并将这个区间扣除。同理,此时剩余的部分皇马净胜球最小,也即曼城净胜球最大。再最大化进球数。
注:如果所有区间中皇马净胜球之和的最大值是负数,就说明所有区间内曼城都占优,不需要扣除任何区间。
,
改为求 的最大值,以及此时以下两个式子的最大值。
改为求 的最大值,以及此时以下两个式子的最大值。
如果 ,直接求 和 的值。
代码调整
代码主要需要更改的部分如下:
- 更改 数组的值(与原值刚好相反);
- 维护双端队列单调递减而非单调递增;
- 记录每次 的最大值而非最小值;
- 时特殊处理。
新代码
CPP#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int M[N], R[N], a[N];
int main(){
string s; getline(cin, s);
// 特判1
if(s == "Who is H?"){
cout << "Erling Braut Haaland";
return 0;
}
int n = s.size();
int k; cin >> k;
// 进球前缀和
for(int i=0; i<n; i++)
M[i+1] = M[i] + (s[i] == 'M'),
R[i+1] = R[i] + (s[i] == 'R');
// 特判2
if(k == 0){
cout << M[n] << ':' << R[n];
return 0;
}
// 净胜球前缀和
for(int i=1; i<=n; i++)
a[i] = R[i] - M[i];
// 改成计算皇马净胜球
deque <int> q;
int maxi = INT_MIN,
Mgoal = 0, Rgoal = 0,
left, right;
for(int i=1; i<=n; i++){
// 单调队列操作
// 1.出队·移除出界元素
if(!q.empty() && q.front()<i-k)
q.pop_front();
// 2.出队·维护单调递增
while(!q.empty() && a[i] <= a[q.back()])
q.pop_back();
// 3.入队·新元素加入
q.push_back(i);
// 避免空队列
if(q.empty()) continue;
int l = q.front(), r = i; // 扣除区间[l+1,r] (求和)
// 净胜球前缀和区间
int dif = a[r] - a[l],
mg = M[r] - M[l], rg = R[r] - R[l];
// 更新答案
if(dif > maxi) // 区间内皇马净胜球增加, 扣除
maxi = dif,
Mgoal = mg, Rgoal = rg,
left = l, right = r;
else if(dif == maxi)
Mgoal = min(mg, Mgoal),
Rgoal = min(rg, Rgoal),
left = l, right = r;
// 净胜球相同情况下, 扣除的进球越少越好(进球多)
}
int Msum = 0, Rsum = 0;
// 如果扣除皇马净胜球是正数
if(maxi > 0)
Msum = Mgoal,
Rsum = Rgoal;
// 扣除
int Mans = M[n] - Msum,
Rans = R[n] - Rsum;
cout << Mans << ':' << Rans;
}
声明
CPPconst int MAX_N = 1e6+5;
调整为如下代码即可 AC。
CPPconst int MAX_N = 1.1e6+5
结尾
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...