26 蓝桥 B组 C/C++
OK哇也是又捐了300块给蓝桥杯官方<(。_。)>
虽然迟到了两周,但还是补完题,写个题解总结一下。
题目链接 https://www.luogu.com.cn/contest/320659
A.青春常数
这题还是比较简单,加1除2或者除2加1都行。
#include <bits/stdc++.h>
using namespace std;
int main() {
long long ans=(2026202520242023+1)/2;
cout<<ans;
return 0;
}
B.双碳战略
答案是 n * 2^(n-1),n = 2026。计算并取模,得到答案792264670。
怎么来的呢?我自己是想不出来的,洛谷题解里大佬讲了推导过程,我选了一个我觉得好理解的,也就是思路一。
思路一
原题解网址:https://www.luogu.com.cn/article/es0tmo4n
设灯光打开的状态为 1,关闭的状态为 0,可以得到一个 01 数组。
我们计数组中联通块的个数为出现连续 0 或 1 的块数,不难发现对于有 i 个联通块的数组 a,最少需要操作 i−1 次或 i 次,由于这两种操作对称,所以问题转化为如何求出 a 的个数。
使用隔板法,对于 i 个联通块,需要 i-1 个隔板,有 n-1 个位置,此时的操作次数可以用组合数来算。而总操作次数就是对 i = 1 到 i = n 的所有组合数求和,即:

对这个式子进行化简,就可以得到 n * 2^(n-1)。
思路二
使用打表法,得出当 n = 3,4,5的情况下,到达各个状态的操作次数。
n = 3

n = 4

整理一下,当 n = 3,操作次数0,1,2,3 对应的状态数依次为 1,3,3,1。
同理可得,当n = 4,操作次数0,1,2,3,4 对应的状态数依次为 1,4,6,4,1。
再打表,当n = 5,操作次数0,1,2,3,4,5 对应的状态数依次为 1,5,10,10,5,1。
注意到(注意力惊人!),最少操作次数为 k 的状态数恰好等于二项式系数 C (n,k)(也是杨辉三角第 n 层的数)。
由此计算总和,所有状态的最少操作次数之和为:

化简后,也可以得到 n * 2^(n-1)。
代码如下,使用快速幂来计算。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll MOD=998244353;
ll ksm(ll a,ll b,ll mod){
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
signed main(){
ll n=2026;
ll ans=n*ksm(2,n-1,MOD)%MOD;
cout<<ans;//ans=792264670
return 0;
}
C.循环右移
读题可发现,想要满足右移后的新数组和原数组完全相同,只有原数组的所有数都相同才行。所以只需计算给定范围X 和Y 中间有多少个数即可。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t;
long long n,x,y;
cin>>t;
while(t--){
cin>>n>>x>>y;
long long ans=y-x+1;
if(ans<=0) cout<<0<<"\n";
else cout<<ans<<"\n";
}
return 0;
}
D.蓝桥竞技
条件一,5 人成团:每支战队由且只能由 5 名选手组成。即选手总数模5 等于0 。
条件二,职业互斥:同一支战队内的 5 名选手,必须来自 5 种完全不同的位置。即Ai 的最大值必须小于等于队伍的数量,否则必定会有至少两个同职位的人被分到一个战队。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int t,n;
long long a[N];
int main() {
cin>>t;
while(t--){
cin>>n;
long long mx=0,sum=0;
for(int i=0; i<n; i++){
cin>>a[i];
mx=max(a[i],mx);
sum+=a[i];
}
if(sum%5==0){ //若可以整除5 ,判断条件二
if(mx<=sum/5) cout<<"T"<<endl;
else cout<<"F"<<endl;
}else{ //否则直接输出 F
cout<<"F"<<endl;
}
}
return 0;
}
E.LQ聚合
贪心,思路如下。
从左到右扫描,维护:
· cntL:当前已经确定是 L 的个数(包括已处理过的 ? 中被定为 L 的)
· ans:当前已累计的 LQ 对数
对于每个 ? ,计算它后面还有多少可能成为 Q 的位置 = 后面已有的 Q 数 + 后面剩余的 ? 数。
如果当前 cntL > 这个数字,说明立即变成 Q 能获得更多(ans += cntL);
否则,变成 L(cntL++),为后续服务。
这样得到的就是最优解。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
string s;
int main() {
cin>>n>>s;
int a[n],cnt1=0; //a[n] 为每个位置后面的Q 的个数,cnt1 为'?' 的总数
a[n]=0;
for(int i=n-1; i>=0; i--){ //倒序遍历数组,计算a[n] 和cnt1
a[i]=a[i+1];
if(s[i]=='Q') a[i]++;
if(s[i]=='?') cnt1++;
}
ll cnt=0,ans=0; //cnt 表示前面的L 的数量
for(int i=0; i<n; i++){
if(s[i]=='L') cnt++;
if(s[i]=='Q') ans+=cnt; //若字符为Q ,则聚合数加上当前L 的数量
if(s[i]=='?'){
cnt1--; //'?' 总数减一,a[i]+cnt1即可表示该位置后面有多少个具有潜力的Q
if(cnt>a[i]+cnt1) ans+=cnt;
else cnt++;
}
}
cout<<ans<<endl;
return 0;
}
F.应急布线
题目简述
实验室有 N 台计算机,目前仅剩 M 条网线正常工作,导致网络分裂成若干个互不相连的连通块。可以架设“应急跳线”来连接这些块,每条跳线连接两台计算机(可以来自不同连通块),且会为这两台计算机各增加 1 条跳线的负载。问:
最少需要的跳线数,以及该最小值下的单台计算机最多接入跳线数的最小可能值。
直接用并查集就行,并不难,但苯人当时并没有想到,,,可恶啊(ノ`⊿´)ノ
思路分析
问题1. 最少跳线数
将现有的每台计算机看作一个点,已有的网线看作边。使用并查集可以求出当前图中的连通块个数。
要使 c 个连通块连通,至少需要 c−1 条边(跳线)。因此 ans1 = c−1。
问题2. 最小化单台计算机的最大跳线数
已知需要添加的跳线总数为 ans1 =c−1。
每条跳线会为两台计算机各贡献 1 度(接入跳线数),所以所有计算机的新增度数总和为 2×ans1 。将这 2×ans1 度分配到 N 台计算机上,就使得每台计算机的度数尽可能平均,从而最小化最大值。
因此 ans2 = (2*ans1)/N(向上取整)
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
vector<int> siz(N,1);
int f[N],n,m;
int find(int x){ //查找+路径压缩
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void unionset(int x,int y){ //按秩合并
x=find(x),y=find(y);
if(x==y) return ;
if(siz[x]<siz[y]) {f[x]=y; siz[y]+=siz[x];}
else {f[y]=x; siz[x]+=siz[y];}
}
int main() {
cin>>n>>m;
for(int i=1; i<=n; i++) f[i]=i;
for(int i=1; i<=m; i++){
int x,y;
cin>>x>>y;
unionset(x,y);
}
int ans=0;
for(int i=1; i<=n; i++){
if(find(i)==i) ans++; //有多少个父节点,就有多少个连通块
}
cout<<ans-1<<" "<<ceil(2.0*(ans-1)/n)<<endl; //注意答案二要向上取整
}
G.理想温度
重述问题
有两个数组 A(当前温度)和 B(理想温度)。一次操作:选择一个连续区间 [l, r] 和一个整数 k,让这个区间内所有 A[i] 变成 A[i] + k。
目标:操作后,让尽量多的位置满足 A[i] == B[i]。
第一步:转化为差值数组
令 a[i]=A[i]−B[i]。一次操作相当于在区间 [l,r] 内给每个 a[i] 加上 k。操作后,位置 i 满足理想温度当且仅当 a[i]+k=0,即 a[i]=−k。
设 c=−k,则我们只需要关注区间内值为 c 的位置(它们会变成 0),以及区间外原本就是 0 的位置(保持不变)。
暴力做法
枚举所有区间 [l,r],统计区间内出现次数最多的值 c(众数),那么:
· 区间内 c 的个数 = 操作后新增的 0 的个数
· 区间外 0 的个数 = 前缀 0 个数 + 后缀 0 个数
· 答案 = 区间内众数个数 + 区间外 0 个数。
暴力枚举所有区间,用 pre 和 suf 数组快速得到区间外 0 的个数,再用 map 统计众数,复杂度 O(n^2 logn),能过 30% 数据。
主要代码如下。
int main() {
int n; cin>>n;
vector<int> a(n+1);
vector<int> pre(n+1);//记录前缀0 的个数
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1,x; i<=n; i++){
cin>>x;
a[i]-=x;
pre[i]=pre[i-1]+(a[i]==0);
}
vector<int> suf(n+1);//记录后缀0 的个数
for(int i=n; i>0; i--){
suf[i]=suf[i+1]+(a[i]==0);
}
int ans=0;
for(int l=1; l<=n; l++){
map<int,int> mp;//存储区间内的每个数及其出现次数
int mx=0;
for(int r=l; r<=n; r++){//枚举区间
mp[a[r]]++;
mx=max(mx,mp[a[r]]);//记录众数的出现次数
ans=max(ans, mx+pre[l-1]+suf[r+1]);//取每次枚举的最大值
}
}
cout<<ans<<endl;
}
优化思路
固定一个 c,定义权值数组 w[i]:
· 若 a[i]=c,则 w[i]=1(选中它将来会变成 0)
· 若 a[i]=0,则 w[i]=−1(原本是 0,被操作后会变成非 0,损失一个)
· 否则 w[i]=0
那么对于一个区间 [l,r],操作后净增的 0 的数量就是就是 w 在区间上的和。
我们的目标变成:对每个 c,求 w 的最大子段和 S(c),最终答案 = 原有 0 的总数 + max(0,maxS(c))。
由于c 的取值只可能是数组a[i](减了b[i]过后的)里出现过的值,且最优子段的两端一定是值为 c 的位置(如果两端是 0 或其他值,可以收缩到最近的 c,净收益不会减少)。因此,我们只需要考虑 c 的所有出现位置,而相邻两个 c 之间的净增益为 cur = 1 - ( pre[r-1] - pre[l] ),其中 l 和 r 分别表示这两个 c 的位置。
利用动态规划求的思想,令 sum 表示以当前 c 结尾的最大子段和,初始 sum = 1(只取第一个 c)。遍历 j=1…m−1,计算 cur、sum = max(1, sum + cur),更新 mx = max(mx, sum)。
最后,该 c 对应的最大净增量为 mx,该 c 能得到的匹配数为 pre[n] + mx。
由于只处理 c 出现的次数,无论 c 为多少,整个循环遍历下来一共只会执行 n 次,所以复杂度为 O(n)。
代码如下。
#include <bits/stdc++.h>
using namespace std;
int n;
int main() {
cin>>n;
vector<int> a(n+1);
for(int i=1; i<=n; i++) cin>>a[i];
vector<int> pre(n+1,0);//存储前缀零
map<int, vector<int>> mp;//存储数c出现的位置
for(int i=1,x; i<=n; i++){
cin>>x;
a[i]-=x;
pre[i]=pre[i-1]+(a[i]==0);
mp[a[i]].push_back(i);
}
int ans=pre[n];//初始答案为差值数组中0的个数
int t0=pre[n];
for(auto & [t,v]:mp){//枚举每个数c
if(t==0) continue;// c=0 时净收益 ≤0,无需处理
int sum=1,mx=1,m=v.size();
for(int i=1; i<m; i++){//枚举每个数c出现的位置
int l=v[i-1], r=v[i];
int cur=1-(pre[r-1]-pre[l]);//从上一个c 到这一个c ,中间的净增益
sum=max(1,sum+cur);//延申的净增量
mx=max(mx,sum);//更新mx 为所有sum 的最大值
}
ans=max(ans,mx+t0);//答案等于原有0 的个数+ 修改后区间内的最大净增益
}
cout<<ans<<endl;
return 0;
}
H.足球训练
参考了这篇题解 https://www.luogu.com.cn/article/pu8f68bq
要让操作后的累乘最大,每次操作就要找到 a/b 最小的一个队员,因为这样操作会让整体增幅最大。
可以举例验证,a = 1,b = 100 和 a=100,b=100 ,选择前者,总乘积会直接翻101倍,而选后者,总乘积只能增加2倍。
暴力思想是,用小根堆按 a/b 排序,每次弹出最小值,执行 a+=b 后放回,重复 m 次。但这样只能拿60分。
100分的做法,由于每次操作后 a/b 会变大,min(x/y) 一定是单调递增的。二分最终所有 min(a/b) 的最大值,二分结果记为 mid。计算每个队员至少需要操作多少次才能使其 a/b ≥ mid(即 a + k*b ≥ mid * b => k ≥ ceil(mid - a/b)),累加操作数 C,若 C ≤ m 则 mid 可行。二分得到最优的 mid 后,先对每个人操作至满足阈值,剩余操作次数 m' < n(若不小于,说明可以再对 n 个人都进行一轮操作,那么之前的 mid 就不是最优的),也就是m'<1e5,这部分就可以套用60分的做法了。
代码如下
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll MOD=998244353;
struct node{
ll x, y, id;//分别为当前实力、天赋、编号
bool operator<(const node & n) const{
ll nx=n.x, ny=n.y;
return nx*y< ny*x;
}
};
int main() {
ll n,m;
cin>>n>>m;
vector<ll> a(n+1),b(n+1);
for(ll i=1; i<=n; i++){
cin>>a[i]>>b[i];
}
auto check = [&](double mid){//定义二分检查函数
ll C=0;
for(ll i=1; i<=n; i++){
double v= 1.0*a[i]/b[i];//比值
ll cur = (ll)ceil(mid-v);
cur = max(0LL, cur);
C+=cur;
if(C>m) return 0;
}
return 1;
};
ll cnt=100;
double l=0, r=2e14;
while(cnt--){//循环100次保证精度
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
priority_queue<node> q;
for(ll i=1; i<=n; i++){
/*根据二分得到的最佳最小值 l ,为每个队员分配基础训练天数cur
使其 实力值/天赋值 不低于 l */
double v= 1.0*a[i]/b[i];
ll cur=(ll)ceil(l-v);
cur = max(0LL, cur);
a[i]+=cur*b[i];
m-=cur;
q.push({a[i],b[i],i});
}
//剩下的用贪心,每次从队列里取 x/y 最大的队员,训练后放回
while(m--){
auto [x,y,id]=q.top();
q.pop();
a[id]+=y;
q.push({a[id],y,id});
}
ll ans=1;
for(ll i=1; i<=n; i++){//计算最终答案
assert(a[i]<=1e18);
a[i]%=MOD;
ans=(ans*a[i])%MOD;
}
cout<<ans<<endl;
return 0;
}
个人总结
现根据个人比赛当天情况,以及补题后的感受,从夯到拉评价各题目的难易程度。
A题,十分简单,拉完了。
B题,比赛的时候看到二进制,猜到了或许要用快速幂,但没有一点思路,打表也没看出规律来,谁曾想居然是杨辉三角……直接给到夯,太难想了。
C题和D题,都挺简单的,思路不难想,代码实现也不难,只能给到 npc。
E题和F题,现在看觉得也不难,贪心和并查集都是会的,但比赛当天脑子晕了,没做出来,直接用的暴力,拿了点点分。介于npc和人上人之间,不给顶级是因为确实不算难;不给npc 是因为自己拉跨了,得分比较低。
G题,这题挺难的,但这题懂了也不是很难(左右脑互博中)。最大子段和知道,但比赛的时候不会;暴力不难,但比赛的时候没想到。这个介于人上人和顶级吧。
H题,比赛的时候靠暴力拿了点点分。感觉这题也好难……完全没想到要找 a/b 最小的,还是题练少了,思维没跟上。理解了为什么要找 a/b 最小的,100分的做法也想不出来。而且大致思路都懂了后,要用代码实现出来也不简单,还是题练少了,编程能力太弱了。这题给到顶级。
总结就是太菜了,还得练啊<(。_。)>

浙公网安备 33010602011771号