2025第十六届蓝桥杯c/c++B组国赛题解

目录

前记:

斐波那契字符串

题目:

思路:

关键代码:

总代码:

项链排列

题目大意:

思路:

总代码:

翻倍

题目大意:

思路:

总代码:

子串去重

题目大意:

思路:

总代码:


前记:

本题解只包含了四道我认为赛时可以做出来的题目,第一道编程题太简单我没加进去,正常比赛中如果做对这五道题其他题的暴力再打满,差不多有90多分国二肯定没问题,国一也差不多。

斐波那契字符串

题目:

斐波那契字符串 S 是由 0 和 1 所组成的字符串,其生成规则如下:

  • S1=0。
  • S2=1。
  • 对于任意正整数 n(n≥3),Sn=Sn−2+Sn−1("+"表示字符串拼接)。

例如:S3​=01、S4​=101、S5​=01101。

在斐波那契字符串 S 中,定义逆序对为满足以下条件的整数对 (i,j):

  • 1≤i<j≤∣S∣(其中 ∣S∣ 表示 S 的长度)。
  • Si=1(第 i 个字符为 1)并且 Sj=0(第 j 个字符为 0)。

现在,给定一个正整数 N,请你计算出 SN​ 中所有逆序对 (i,j) 的总数。由于结果可能很大,请输出其对 109+7 取余后的值。

对于 20% 的评测用例,1≤T≤20,3≤N≤35。

对于 100% 的评测用例,1≤T≤,3≤N≤

思路:

NT都很大,如果对于每一个t都重新求出对于的斐波那契字符串然后再求其逆序对数的话定然得不了全分,此时我们就开始想优化 ,很明显一个思路方向是:每个新字符串都是由前两个字符串得来的,那么每个新字符串的逆序对数是否也和前两个字符串有关系呢?

答案是显而易见的,必然有关系!

新字符串的逆序对数=前一个字符串的逆序对数+前二个字符串的逆序对数+两个字符串合体之后新增的逆序对数 ,现在关键就是要确定两个字符串合体之后新增的逆序对数,求出之后直接递推即可,新增逆序对数很明显就是前二个字符串中1的个数*前一个字符串中0的个数,现在问题又变成了求各个字符串中0和1的个数。

这个还是很好求的,也是直接递推上去就行。

关键代码:

cpp 复制代码
for(int i=3; i<N; i++) {
        cnt0[i] = (cnt0[i-1] + cnt0[i-2]) % mod;  
        cnt1[i] = (cnt1[i-1] + cnt1[i-2]) % mod;  
    }
    
    for(int i=3; i<N; i++) {
        dp[i] = (dp[i-1] + dp[i-2]) % mod;
        dp[i] = (dp[i] + (cnt1[i-2] * cnt0[i-1]) % mod) % mod;
    }

总代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int mod=1e9+7;
const int N=1e5+10;
int dp[N];
int cnt0[N],cnt1[N];

void solve(){
    int n;
    cin>>n;
    cout << dp[n]%mod << endl;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t=1;
    cin>>t;
    
    dp[1]=dp[2]=0;
    cnt0[1]=1, cnt1[1]=0;
    cnt0[2]=0, cnt1[2]=1;  
    
    for(int i=3; i<N; i++) {
        cnt0[i] = (cnt0[i-1] + cnt0[i-2]) % mod;  
        cnt1[i] = (cnt1[i-1] + cnt1[i-2]) % mod;  
    }
    
    for(int i=3; i<N; i++) {
        dp[i] = (dp[i-1] + dp[i-2]) % mod;
        dp[i] = (dp[i] + (cnt1[i-2] * cnt0[i-1]) % mod) % mod;
    }
    
    while(t--)
        solve();
}

项链排列

题目大意:

给定a个'L',b个'Q',让你拼接成一个字符串,L和Q如果相邻放置,则该字符串变化次数+1,例如:LQ的变化次数是1,LQL的变化次数是2,LQQLLQ的变化次数是3

现在给你一个指定变化次数c,问你如何对这个字符串进行排列才能确保该字符串的变化次数为c,输出字典序最小的那个字符串

思路:

本题是一道贪心

我们先来看一下有哪几种情况会增加变化次数

如上图所示,共有两大种情况,每种情况又分为两种小情况,因为题目要求字典序最小,所以肯定是第一种情况最优,那么此时就需要确定什么时候才满足第一种情况,什么时候满足第二种情况

通过观察很明显发现第一种情况所需的L数>=第二种情况的L数,但是Q数却正好相反,那么巨头到底L数和Q数的满足条件是什么呢?

通过找几个例子就能得到:

cpp 复制代码
 int lneed1=c/2+1,qneed1=(c+1)/2;
 int lneed2=(c+1)/2,qneed2=c/2+1;

对于满足第一种的:

此时可能会有多出的L和多出的Q,此时再考虑安置这些多出来字符

此时需要分两种小情况,例如图中对于第一种小情况,多出来的L直接插到最前面即可,多出来的Q需要插到最后一个Q的后面,也就是最后的字符L前面

而对于第二种小情况,L直接插到最前面不变,此时Q直接插到最后面即可

对于不满足第一种,却满足第二种的:

此时我们发现,定然不会存在多出来的L,否则定然满足第一种条件,此时只需插入多出的Q即可,此时也需分为两种小情况,第一个小情况定然不会存在,因为可以转换成LQLQ,只会存在第二种小情况,此时多出来的Q直接插到最后即可

不满足第一种也不满足第二种的直接输出-1

注意需要特判c=0的情况

总代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int mod=1e9+7;
const int N=1e5+10;
void solve(){
    int a,b,c;
    cin >> a >> b >> c;
    if (c==0) {
        if (a>0&&b>0) {
            cout << -1;
            return ;
        }
        for (int i=0;i<a;i++) {
            cout << 'L';
        }
        for (int i=0;i<b;i++) {
            cout << 'Q';
        }
        return ;
    }
    int lneed1=c/2+1,qneed1=(c+1)/2;
    int lneed2=(c+1)/2,qneed2=c/2+1;
    if (a>=lneed1&&b>=qneed1) {
        int cnt=0;
        vector<char>ans;
        ans.push_back('L');
        ans.push_back('Q');
        cnt++;
        int n=max(a,b)*2;
        a--,b--;
        for(int i=2;i<n;i++) {
            if (cnt==c) {
                break;
            }
            if (i%2==0) {
                ans.push_back('L'),a--,cnt++;
            }
            else
                ans.push_back('Q'),b--,cnt++;
        }
        if (ans.back()=='Q') {
            // cout << 123244 << endl;
            for (int i=0;i<a;i++) {
                cout << 'L';
            }
            for (int i=0;i<ans.size();i++) {
                cout << ans[i];
            }
            for (int i=0;i<b;i++) {
                cout << 'Q';
            }
        }else {
            for (int i=0;i<a;i++) {
                cout << 'L';
            }
            for (int i=0;i<ans.size();i++) {
                if (i!=ans.size()-1)
                cout << ans[i];
                else {
                    for (int j=0;j<b;j++) {
                        cout << 'Q';
                    }
                    cout << 'L';
                    i++;
                }
            }
        }
    }else if(a>=lneed2&&b>=qneed2) {
        int cnt=0;
        vector<char>ans;
        ans.push_back('Q');
        ans.push_back('L');
        cnt++;
        int n=max(a,b)*2;
        a--,b--;
        for(int i=2;i<n;i++) {
            if (cnt==c) {
                break;
            }
            if (i%2==0) {
                ans.push_back('Q'),b--,cnt++;
                // cout << 1323 << endl;
            }else
                ans.push_back('L'),a--,cnt++;

        }
        for (int i=0;i<ans.size();i++) {
            if (i!=ans.size()-1)
                cout << ans[i];
            else {
                for (int j=0;j<b;j++) {
                    cout << 'Q';
                }
                cout << 'Q';
                i++;
            }
        }
    }else {
        cout << -1 ;
    }
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t=1;
    // cin>>t;
    while(t--)
        solve();
}

翻倍

题目大意:

给定一个长度为n正整数序列,每次操作可以使得其中一个数翻倍,问你最低操作多少次可以使得该序列整体非递减。

思路:

暴力思路很好想,直接遍历整个数组,如果该数小于前面的数那么该数就一直循环倍增,直至大于等于前面的数

cpp 复制代码
int ans = 0;
for(int i = 2; i <= n; i++) {
	while(a[i] < a[i - 1]) {
		ans++;
		a[i] *= 2;
	}
}
cout << ans << endl;

但这个肯定得不了满分,我们此时考虑一下优化,优化的主要部分就是这个while循环,我们仔细一想会发现,当前数翻倍的次数只与前一个数有关,为了保证两数的相对大小关系不变 ,前面的数翻多少倍,那么后面的数同样翻多少倍,此时就会出现一个问题:后面的数有可能多翻好多倍,也有可能少翻了好多倍

此时我们再对倍数进行调整,多翻了就循环--,少翻了就循环++,注意是翻倍所以调整的过程时间复杂度是log级别

cpp 复制代码
for (int i=1;i<n;i++) {
        cnt[i]=cnt[i-1];
继承前面数的翻倍情况,保证相对大小不变
        int x=a[i],y=a[i-1];
        while (x>=y*2) {
            if (!cnt[i])break;
            cnt[i]--;
            y*=2;
        }
注意循环条件,只有x>=y*2时才会存在多翻的情况
        while (x<y) {
            cnt[i]++;
            x*=2;
        }
x<y代表少翻了,还得继续翻倍
    }

总代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int mod=1e9+7;
const int N=2e5+10;
int a[N],cnt[N];
void solve(){
    int n;
    cin >> n;
    for (int i=0;i<n;i++) {
        cin >> a[i];
    }
    for (int i=1;i<n;i++) {
        cnt[i]=cnt[i-1];
        int x=a[i],y=a[i-1];
        while (x>=y*2) {
            if (!cnt[i])break;
            cnt[i]--;
            y*=2;
        }
        while (x<y) {
            cnt[i]++;
            x*=2;
        }
    }
    int ans=0;
    for (int i=0;i<n;i++) {
        ans+=cnt[i];
    }
    cout << ans << endl;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t=1;
    // cin>>t;
    while(t--)
        solve();
}

子串去重

题目大意:

给定一个字符串和m次询问,每次询问给两个区间,问你两个区间去重之后有多少个对应位置的字符不同,字符串全是小写字母

思路:

我们可以发现去重之后两个被询问的字串长度定然<=26,此时就可以直接遍历求得答案,主要是如何去重。

我们从字串长度必然小于等于26这个地方起手,我们可以不遍历区间,而是从0-25遍历每个字符,看这个字符在该区间第一次出现的位置

我们需要提前预处理一个二维数组aij:从i开始字符j第一次出现的位置

cpp 复制代码
vector<vector<int>>a(n+2,vector<int>(26,n+1));
	for (int i=n;i>=1;i--) {
		a[i]=a[i+1];
		a[i][s[i]-'a']=i;
	}

求出来之后即可完成上面的想法:如果第一次出现的位置<区间右端点便可以插入到数组里

cpp 复制代码
int l1,l2,r1,r2;
		cin >> l1 >> r1 >> l2 >> r2;
		vector<node>a1,a2;
		for (int i=0;i<26;i++) {
			if (a[l1][i]<=r1)
				a1.emplace_back(i,a[l1][i]);
		}
		for (int i=0;i<26;i++) {
			if (a[l2][i]<=r2)
				a2.emplace_back(i,a[l2][i]);
		}

注意插入完需要根据位置进行排序

总代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int mod=1e9+7;
const int N=2e5+10;
int n;
struct node {
	int aa,bb;
	node(int a, int b) : aa(a), bb(b) {}
};
bool cmp(struct node a,struct node b) {
	return a.bb<b.bb;
}
void solve() {
	string s;
	cin >> s;
	s=" "+s;
	int m;
	n=s.size()-1;
	vector<vector<int>>a(n+2,vector<int>(26,n+1));
	cin >> m;
	for (int i=n;i>=1;i--) {
		a[i]=a[i+1];
		a[i][s[i]-'a']=i;
	}
	while (m--) {
		int l1,l2,r1,r2;
		cin >> l1 >> r1 >> l2 >> r2;
		vector<node>a1,a2;
		for (int i=0;i<26;i++) {
			if (a[l1][i]<=r1)
				a1.emplace_back(i,a[l1][i]);
		}
		for (int i=0;i<26;i++) {
			if (a[l2][i]<=r2)
				a2.emplace_back(i,a[l2][i]);
		}
		sort(a1.begin(),a1.end(),cmp);
		sort(a2.begin(),a2.end(),cmp);
		int ans=0;
		for (int i=0;i<min(a1.size(),a2.size());i++) {
			if (a1[i].aa!=a2[i].aa)ans++;
		}
		ans+=max(a1.size(),a2.size())-min(a1.size(),a2.size());
		cout << ans << endl;
	}

}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t=1;
    // cin>>t;
    while(t--)
        solve();
}
相关推荐
超梦dasgg1 小时前
Tarjan算法解 强连通分量 & 循环依赖
算法·深度优先·图论
努力努力再努力wz1 小时前
【Qt入门系列】:QLabel控件详解:从文本显示到图片展示,再到内容布局与伙伴机制
android·开发语言·数据结构·数据库·c++·qt·mysql
mN9B2uk171 小时前
MySQL命令行导出数据库
c语言·数据库·mysql
散峰而望1 小时前
【算法练习】算法练习精选:从 Phone numbers 到 Decrease,覆盖字符串、模拟、图论思维题
数据结构·c++·算法·贪心算法·github·动态规划·图论
薇茗2 小时前
【C++】 基础语法篇
c++·c++基础语法
人道领域2 小时前
【LeetCode刷题日记】538.把二叉搜索树转换为累加树
java·开发语言·后端·算法·leetcode
并不喜欢吃鱼2 小时前
从零开始 C++----- 十二【C++ 数据结构】map/set 全解析:从使用到红黑树底层模拟实现
开发语言·数据结构·c++
zlinear数据采集卡2 小时前
电源纹波无处遁形!工业采集卡电源去耦与滤波电路深度解析
c语言·嵌入式硬件·fpga开发·自动化·硬件架构
不会C语言的男孩2 小时前
C++ Primer Plus 第17章:输入、输出和文件
开发语言·c++