在这里总结一下从放暑假开始到现在断断续续刷蓝桥杯真题遇到的算法思路,从第十届一直倒着刷到第四届,总的来说蓝桥杯的出题思路在中等题搜索和回溯是一直在变少的,但是搜索的花样变得更多了,而且中等题中的暴力题也一直在减少,关于数论的题也同时在出现,对参赛学生的要求也是变高的,而对于前面的几道题来说同样的花样变得多了起来,越到后面的几届的蓝桥杯的简单题是需要你停下来动脑子想一下才能出思路的,就这样,最后一题暂且不谈。
其实最重要的一步是要正确理解题,仔细认真即可。
dp
在真题当中动态规划还是比较少的,到现在为止只遇到了两个,一个是完全背包动归,还有一个dp:
dp找到状态转移方程一切就都好说了,还有要明确自己定义的dp数组的含义。
2017包子凑数
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入
----
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出
----
一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
例如,
输入:
2
4
5
程序应该输出:
6
再例如,
输入:
2
4
6
程序应该输出:
INF
样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
对于样例2,所有奇数都凑不出来,所以有无限多个。
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms
//数论-扩展欧几里得 + 完全背包动态规划
#include<iostream>
#include<cstring>
#include<cstdio>
#define MAXN 10010
using namespace std;
int dp[MAXN];
int a[105];
int n;
int pd(int a,int b) {
//递归最大公约数
if (b==0) return a;
else return pd(b,a%b);
}
int main() {
scanf("%d",&n);
int temp = 0;
for (int i = 1;i<=n;i++) {
scanf("%d",&a[i]);
if (i==1) temp = a[i];
else temp = pd(temp,a[i]);
}
if (temp!=1) printf("INF");//只要包子笼的容量中存在一对数据最大公约数为1,即互质,那么这个包子数目就是可以凑数出来的。
else {
dp[0] = true;
for (int i = 1;i<=n;i++) {
for (int j = 0;j+a[i]<=MAXN;j++) {
if (dp[j]) dp[j+a[i]] = true;
}
}
int cnt = 0;
for (int i = 0;i<=MAXN;i++) {
if (dp[i]==false) cnt++;
}
printf("%d",cnt);
}
return 0;
}
2018测试次数
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
请填写这个最多测试次数。
#include<iostream>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
//动归解题
using namespace std;
int main() {
int dp[1005][10];//dp[x][y]表示当前还有x楼不确定,y个没坏的手机
int n,m;
cin>>n>>m;
for (int i = 1;i<=n;i++) {
dp[i][1] = i;
}
for (int cnt = 2;cnt<=m;cnt++) {
for (int ind = 1;ind<=n;ind++) {
dp[ind][cnt] = 1 + dp[ind-1][cnt];//最坏的运气,直接不确定的楼层加上一
for (int k = 2;k<=ind;k++) {
dp[ind][cnt] = min(dp[ind][cnt],1+max(dp[k-1][cnt-1],dp[ind-k][cnt]));//在摔坏一部手机增加测试次数与不动手机增加测试次数之间寻找最坏运气
}//状态转移方程直接在dp矩阵以dp[ind][cnt]与dp[1][1]之间先max寻找最小也就是最坏运气,再min寻找最佳策略
}
}
cout<<dp[n][m];
return 0;
}
这里给一个完全背包dp的板子:
#include<iostream>
using namespace std;
int dp[101][101];
int main() {
int n,g;
cin>>n;
int w[105],v[105];
for (int i = 0;i<n;i++) {
cin>>w[i];
}
for (int i = 0;i<n;i++) {
cin>>v[i];
}
cin>>g;
//初始化
for (int i = 0;i<=g;i++) {
dp[0][i] = i/w[0]*v[0];
} //只能选择第一个物品时
for (int i = 0;i<n;i++) {
dp[i][0] = 0;
}//背包容量为零
int max = 0;
for (int i = 1;i<n;i++) {
for (int j = 1;j<=g;j++) {
for (int k = 0;k*w[i]<=j;k++) {
int temp = k*v[i] + dp[i-1][j-k*w[i]];
if (max<temp) max = temp;
}
dp[i][j] = max;
max = 0;
}
}
cout<<dp[n-1][g]<<endl;
return 0;
}
回溯
蓝桥杯的回溯题可就多了去了,但是蓝桥杯的回溯题总是让我想到用全排列或者求子集的回溯模板来解决,但是题目多种多样,需要在回溯完依次验证结果即可。
2016凑算式
这个算式中AI代表1-9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
直接回溯模板全排列最后验证,当然最好使用next_permutation()函数快
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int sums = 0;
void solve(vector<int>& track) {
int s1,s2,s3;
s1 = track[1]*(track[6]*100+track[7]*10+track[8]);
s2 = track[2]*(track[3]*100+track[4]*10+track[5]);
s3 = (10-track[0])*track[2]*(track[6]*100+track[7]*10+track[8]);
if (s1+s2==s3) sums++;
}
void backtrack(vector<int>& track,vector<int>& nums) {
if (track.size()==9) solve(track);
for (int i = 0;i!=nums.size();i++) {
track.push_back(nums[i]);
int temp = nums[i];
nums.erase(nums.begin()+i);
backtrack(track,nums);
track.pop_back();
nums.insert(nums.begin()+i,temp);
}
}
int main() {
int x1,x2,x3;
int sum = 0;
vector<int> a(9,0);
vector<int> track;
for (int i = 0;i<9;i++) {
a[i] = i+1;
}
while(next_permutation(a.begin(),a.end())) {
x1 = a[1]*(a[6]*100+a[7]*10+a[8]);
x2 = a[2]*(a[3]*100+a[4]*10+a[5]);
x3 = (10-a[0])*a[2]*(a[6]*100+a[7]*10+a[8]);
if (x1+x2==x3) sum++;
}
cout<<sum<<endl;
backtrack(track,a);
cout<<sums<<endl;
return 0;
}
2017方格填数:
图,如下的10个格子,填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)一共有多少种可能的填数方案?
请填写表示方案数目的整数。
回溯:
#include<iostream>
using namespace std;
int a[3][4];
int sum = 0;
void backtrack(int x,int y,int n){
if (n==9) {
sum++;return ;
}//在寻找最后一个数9时实际上已经确定了位置,填法加一
for (int i = 0;i<3;i++