本人26年国二,讲解备赛过程中做过的模拟赛。
比赛说明:本次比赛由东北大学秦皇岛分校、安徽师范大学、安徽工业大学、安徽农业大学、阜阳师范大学、郑州轻工业大学、武昌首义学院联合举办,为蓝桥杯参赛选手们提供两场免费的蓝桥杯模拟赛。赛制与题型均参考蓝桥杯。
文章目录
- [蓝桥杯多校模拟赛 题解](#蓝桥杯多校模拟赛 题解)
-
- [A. 无限重复序列](#A. 无限重复序列)
- [B. 子序列数字和整除](#B. 子序列数字和整除)
- [C. 天气预报](#C. 天气预报)
- [D. 重复子串计数](#D. 重复子串计数)
- [E. 反向数字三角形](#E. 反向数字三角形)
- [F. 手套配对最小最大差值](#F. 手套配对最小最大差值)
- [G. 优雅字符串最小交换次数](#G. 优雅字符串最小交换次数)
- [H. 最短全覆盖子序列](#H. 最短全覆盖子序列)
蓝桥杯多校模拟赛 题解
A. 无限重复序列
题意
由字符串 "2026" 无限拼接形成序列,求该序列前 202620262026 项中字符 '2' 的总个数。
思路
序列以 "2026" 为周期循环,周期长度为4,每个周期内包含2个 '2'。
- 计算完整周期数量:
full = num / 4,共贡献2 * full个'2'。 - 计算剩余字符数
rem = num % 4,统计周期前rem位中'2'的个数。本题中202620262026 % 4 = 2,前两位为"20",额外贡献1个'2'。
最终结果为 2 * (202620262026 / 4) + 1,直接计算即可得到答案。
B. 子序列数字和整除
题意
将 "2026" 重复2026次形成长度为 4×2026 的数字序列,从中任选3位组成子序列,求满足三位数字之和能被6整除的子序列数量,结果对 10^9+7 取模。
思路
先统计各数字的出现次数:每个周期 "2026" 含1个0、2个2、1个6,重复2026次后,cnt0 = 2026,cnt2 = 2*2026,cnt6 = 2026。
数字0和6模6均为0,数字2模6为2,仅当三元组全为模0、或全为模2时,和能被6整除:
- 三个数均为0或6 :任意组合的和都能被6整除,组合数等价于
C(cnt0 + cnt6, 3),可拆分为三个0、两0一六、一零两六、三个6四种情况分别计算。 - 三个数均为2 :和为
2×3=6,能被6整除,组合数为C(cnt2, 3) = C(2×2026, 3)。
通过组合数公式结合逆元处理取模,将两种情况的结果相加模 1e9+7 即可。
正解代码
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+9,M=1e9+7;
int n,m,a[N],ans,cnt;
//逆元 a/b%M = a*ksm(b,m-2)
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%M;
b>>=1;
a=a*a%M;
}
return res;
}
int C(int a,int b){
if(b==1)return a;
if(b==2)return a*(a-1)%M*ksm(2,M-2)%M;
if(b==3)return a*(a-1)%M*(a-2)%M*ksm(6,M-2)%M;
return 1;
}
signed main(){
ans+=C(2026,3)%M;
ans+=C(2026,1)%M*C(2026,2)%M;
ans+=C(2026*2,3)%M;
ans+=C(2026,1)%M*C(2026,2)%M;
ans+=C(2026,3)%M;
cout<<ans%M;
return 0;
}
C. 天气预报
题意
给定连续n天的天气类型,预报规则为:
- 第1天预报天气类型1。
- 前一天预报正确则当天预报不变;预报错误则统计此前所有天中出现次数最多的天气(次数相同时选首次出现最晚的)作为当天预报。
求预报成功的总天数。
思路
线性模拟预报过程,实时维护当前最优天气,时间复杂度O(n):
- 用数组
cnt[x]记录天气x的出现次数,ti[x]记录天气x首次出现的天数。 - 维护变量
cur表示当前出现次数最多、且首次出现最晚的天气,mx记录当前最高出现次数。 - 变量
last表示当天的预报值,初始为1。 - 遍历每天的天气:
- 更新当前天气的计数与首次出现时间,若该天气刷新最高次数,或次数持平但首次出现更晚,则更新
cur。 - 若预报值
last与当天天气一致,答案+1;否则将次日预报值更新为cur。
- 更新当前天气的计数与首次出现时间,若该天气刷新最高次数,或次数持平但首次出现更晚,则更新
正解代码
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+9;
int n,m,a[N],ans,cnt[N],ti[N];
bool st[N];
signed main(){
cin>>n;
int x,mx=0,last=1,cur;
bool fg=0;
for(int i=1;i<=n;i++){
cin>>x;
if(!st[x]){st[x]=1;ti[x]=i;}
cnt[x]++;
if(cnt[x]>mx||(cnt[x]==mx&&ti[x]>ti[cur])){
mx=cnt[x];
cur=x;
}
if(last==x){
//cout<<x<<' ';
ans++;
}
else{
last=cur;
}
}
cout<<ans;
return 0;
}
D. 重复子串计数
题意
给定长度为n、仅由F/B/L/R组成的字符串,统计出现次数大于1的长度为k的连续子串的数量。
思路
采用哈希统计思路,实现简单且适配题目k≤10的数据范围:
- 遍历字符串,截取所有长度为k的连续子串。
- 用哈希表记录每个子串的出现次数。
- 遍历哈希表,统计出现次数大于1的子串数目。
正解代码
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+9;
int n,m,a[N],ans,cnt,k;
map<string,int>mp;
string s;
signed main(){
cin>>n>>k>>s;
for(int i=0;i+k<n;i++){
string s1=s.substr(i,k);
mp[s1]++;
}
for(auto t:mp){
if(t.second>1)ans++;
}
cout<<ans;
return 0;
}
E. 反向数字三角形
题意
已知n和最终数字和sum,原始序列是1~n的排列。将序列相邻两项相加得到新序列,重复操作直到只剩一个数等于sum。求满足条件的字典序最小的原始排列。
思路
该过程本质是杨辉三角加权求和:第i个位置的数最终会乘以系数 C(n-1, i-1)(组合数),所有项的加权和即为最终sum。问题转化为寻找1~n的排列,使得加权和等于sum且字典序最小。
采用深度优先搜索(DFS)求解:
- 预处理计算n-1阶杨辉三角的系数数组。
- 按位置从左到右枚举排列,从小到大尝试未使用的数字,累加当前加权和。
- 剪枝:若当前加权和已超过sum,直接回溯。
- 由于按从小到大枚举数字,第一个找到的合法排列即为字典序最小的解,找到后直接输出并退出。
结合剪枝后可高效通过n≤12左右的数据规模。
正解代码
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+9;
int n,m,sum,mi=13,c[16];
int p[16];
bool st[15];
//暴力可排序 或直接倒着枚举第一个=sum即为最大
/* 杨辉三角模板
vector<int> coef(n + 1);
coef[0] = 1;
for(int i = 1; i <= n; i++) {
coef[i] = coef[i - 1] * (n - i) / i;
}
*/
void dfs(int u,int cnt){
if(cnt>sum)return ;
if(u==n){
if(cnt==sum){
for(int i=0;i<n;i++)cout<<p[i]<<' ';
exit(0);
}
return ;
}
for(int i=1;i<=n;i++){
if(!st[i]){
st[i]=1;
p[u]=i;
dfs(u+1,cnt+i*c[u]);
st[i]=0;
p[u]=0;
}
}
}
signed main(){
cin>>n>>sum;
if(n==1){
cout<<1;
return 0;
}
c[0]=1;
for(int i=1;i<n;i++)c[i]=c[i-1]*(n-i)/i;
dfs(0,0);
return 0;
}
F. 手套配对最小最大差值
题意
n只左手手套、m只右手手套各有尺码,配对丑陋程度为尺码差的绝对值。要求配对尽可能多的手套,并使得所有配对中的最大丑陋程度最小,求该最小值。
思路
典型的"最大值最小化"问题,采用二分答案 + 贪心验证求解:
- 二分答案 :二分枚举最大允许的差值x,判断能否配对满
min(n,m)副手套。 - 贪心验证 :
- 将左右手套尺码分别从小到大排序,保证左手为数量较少的一侧。
- 双指针遍历:若当前左右手套差值≤x则配对成功,双指针同时右移;否则右移右手指针尝试更大的右手尺码。
- 若最终配对数等于左手总数,则x可行。
- 二分边界为0到尺码最大值差,最终得到最小的可行x。
时间复杂度为O((n+m)log(max_size)),适配n,m≤1e5的数据范围。
正解代码
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+9;
int n,m,a[N],b[N],ans;
bool ck(int x){
int cnt=0;//配对数
int i=1,j=1;
for(;i<=n&&j<=m;j++){
if(abs(a[i]-b[j])<=x){
i++;
cnt++;
}
}
return cnt>=n;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
if(n>m){
for(int i=1;i<=n;i++)swap(a[i],b[i]);
swap(n,m);
}
sort(a+1,a+1+n);
sort(b+1,b+1+m);
int l=0,r=1e9+7;
while(l<r){
int mid=(l+r)/2;
if(ck(mid))r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
G. 优雅字符串最小交换次数
题意
字符串仅包含0、1、2,定义"优雅"为没有相邻相同的字符。每次可交换相邻字符,求将字符串变为优雅的最少操作次数,无解输出-1。
思路
暴力思路(30%)
枚举所有合法的优雅排列,计算原串变为该排列的相邻交换次数,取最小值。但n=400时排列数过多,无法通过全量数据。
正解:三维动态规划
- 无解判定 :若任意字符的数量超过
(n+1)/2,则无法构造优雅字符串,直接返回-1。 - 核心性质:相同字符的相对顺序不会改变最少交换次数,最优解中同字符的相对顺序与原串一致。可预处理每个字符在原串中的位置列表。
- DP状态定义 :
dp[i][j][k][c]表示使用i个0、j个1、k个2,且最后一个字符为c(c∈{0,1,2})时的最少交换次数。 - 状态转移:枚举下一个放置的字符(不能与末尾字符相同),计算将对应字符移动到当前末尾的新增代价(已选字符中位置大于当前字符原位置的数量),更新dp值。
- 最终答案:取用完所有字符的三个状态中的最小值。
总状态数约为O(cnt0×cnt1×cnt2×3),n=400时可在合理时间内通过。
H. 最短全覆盖子序列
题意
给定长度为n、数值在1 ~ k之间的数列,支持两种操作:修改某个位置的数值;查询包含所有1~k数值的最短连续子序列的长度,无解输出-1。
思路
暴力解法(50%)
每次查询时使用双指针(滑动窗口)算法:维护窗口内各数字的出现次数与种类数,右指针扩展窗口,种类数达到k后左指针收缩窗口,记录最小窗口长度。单次查询O(n),总复杂度O(mn),适合小数据场景。
暴力代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=5010;
int a[N];
int n,k,m;
void f(){
unordered_map<int,int>mp;
vector<bool>st(n+1);
int l=1,ans=1e9;
for(int r=1;r<=n&&l<=n;r++){
mp[a[r]]++;
while(mp.size()==k){
ans=min(ans,r-l+1);
mp[a[l]]--;
if(!mp[a[l]])mp.erase(a[l]);
l++;
}
}
if(ans==1e9)cout<<-1;
else cout<<ans;
cout<<'\n';
}
int main(){
cin>>n>>k>>m;
for(int i=1;i<=n;i++)cin>>a[i];
while(m--){
int op,p,v;
cin>>op;
if(op==1){
cin>>p>>v;
a[p]=v;
}
else f();
}
return 0;
}
正解:线段树 + 状态压缩