蓝桥杯训练-3.16
代码练习
• 幂方分解
问题描述
任何一个正整数都可以用2的幂次方表示。例如:
137=27+23+20
同时约定方次用括号来表示,即ab 可表示为a(b)。
由此可知,137可表示为:
2(7)+2(3)+2(0)
进一步:7= 22+2+20 (21用2表示)
3=2+20
所以最后137可表示为:
2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如:
1315=210 +28 +25 +2+1
所以1315最后可表示为:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入格式
输入包含一个正整数N(N<=20000),为要求分解的整数。
输出格式
程序输出包含一行字符串,为符合约定的n的0,2表示(在表示中不能有空格)
思路:
根据题意,可知递归的思想,首先该题是用递归实现的,我们先要确定,最后所有的指数都会被分解成0或者是1,所以设置递归的终点:递归结束的条件是当n等于1或者是等于2的时候,输出2(0)或2并return。接下来是分解的过程,先设定一个cnt用来当2的指数,p为具体2的多少次方的值,用while循环,每循环一次,p * 2,cnt++,直到p>n,循环结束,此时p / 2(因为在结束循环之前,又乘了个2)是最接近n的数,指数为 cnt - 1,接下来就是讨论 p / 2的情况了,
① 如果刚好p / 2 = n的话,那么 p / 2必定是2的几次方,不带任何后面的,直接打印格式,然后对指数调用一次函数,因为指数可能不是最简形式。
② 如果 p / 2 < n的话,(1)可能p / 2 == 2,那么只需要打印一个2即可,不带次方 (2)如果不是,那么先把最大次幂 p / 2打印出来,然后在把剩下的 n - p / 2调用函数,继续递归
代码:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <string>
#include <cstdio>
using namespace std;
int N;
void f(int n)
{
if(n == 1)
{
printf("2(0)");
return;//一定要return
}
if(n == 2)
{
printf("2");
return;
}
int p = 1;//p为具体2的多少次方的值
int cnt = 0;//记录传过来的数乘了多少次2
while(p <= n)
{
p *= 2;
cnt++;
}
cnt--;//要减去一次,因为这次p刚好大于n,不算数
if(n == p / 2)//如果n刚好是2的某次方
{
printf("2(");
f(cnt);//那么只需要把幂转化成 2,0的形式即可,调用一次函数即可,这里开始递归
printf(")");//同时左右两侧输出好格式
}
else//如果n大于 p / 2的话,那么此时2^cnt是最接近n是2次方数
{
if(p / 2 == 2)//如果p在乘完2以后是在(4,8)的区间里面,也就是p原本是2或者3时,因为2的一次方显示2
{
printf("2+");
f(n - p / 2);
}
else
{
printf("2(");
f(cnt);//首先把最大的次幂显示出来
printf(")+");
f(n - p / 2);//剩下的再调用函数,显示出来
}
}
}
int main()
{
cin >> N;
f(N);
return 0;
}
• 拦截导弹
问题描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,为导弹依次飞来的高度
输出格式
两行,分别是最多能拦截的导弹数与要拦截所有导弹最少要配备的系统数
样例输入
389 207 155 300 299 170 158 65
样例输出
6
2
思路:
本题的题意是下一次拦截的导弹高度不能高于上一次的导弹,要求最多能拦截多少导弹,本质上就是个求最长下降子序列的题目(这里的一个系统可以先不拦截一个导弹,然后等到有可以拦截的导弹再拦截),那就很好用DP动态规划的思想去实现了,然而最少要配备的系统数,这里可以用到一个结论:根据Dilworth定理:最少的下降子序列个数为最长不下降子序列的长度,上升子序列同理,
这里最少要配备的系统数,本质上就是求导弹高度组成的序列中最少的下降子序列个数,所以我们只需要再求一共最长不下降子序列的长度即可。
还可以用贪心的算法实现,策略为:每颗导弹来袭时,使用能拦截这颗导弹的防御系统中上一次拦截导弹高度最低的那一套来拦截。若不存在符合这一条件的系统,则使用一套新系统。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100007;
int a[maxn], dp1[maxn], dp2[maxn];//这里的dp1[i]表示以第i个导弹为拦截结尾的最多拦截导弹个数
int main() //dp2[i]表示以第i个数为结尾的最长不下降子序列的长度
{
int num = 0;
while(1){
scanf("%d",&a[++num]);
char ch=getchar();
if(ch=='\n')break;
}
int ans = 0;//保存最多拦截导弹的个数
int cnt = 0;//保存最长不下降子序列的最大长度
for(int i = 1;i <= num; i++)
{
dp1[i] = 1;//先初始化为1
dp2[i] = 1;
for(int j = 1;j <= num; j++)
{
if(j < i)
{
if(a[j] > a[i])
{
dp1[i] = max(dp1[i], dp1[j] + 1);//选择大的
}
else// 也就是a[j] >= a[i],也就是不下降
{
dp2[i] = max(dp2[i], dp2[j] + 1);
}
}
ans = max(dp1[i], ans);//每次找完第i个导弹结尾,都得保存最大值
cnt = max(dp2[i], cnt);//cnt同理
}
}
cout << ans << endl;
cout << cnt << endl;
return 0;
}
• 回文数(进制转化-高精度加法)
问题描述
若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文数。
例如:给定一个10进制数56,将56加65(即把56从右向左读),得到121是一个回文数。
又如:对于10进制数87:
STEP1:87+78 = 165 STEP2:165+561 = 726
STEP3:726+627 = 1353 STEP4:1353+3531 = 4884
在这里的一步是指进行了一次N进制的加法,上例最少用了4步得到回文数4884。
写一个程序,给定一个N(2<=N<=10或N=16)进制数M(其中16进制数字为0-9与A-F),求最少经过几步可以得到回文数。
如果在30步以内(包含30步)不可能得到回文数,则输出“Impossible!”
输入格式
两行,N与M
输出格式
如果能在30步以内得到回文数,输出“STEP=xx”(不含引号),其中xx是步数;否则输出一行”Impossible!”(不含引号)
样例输入
9
87
样例输出
STEP=6
思路:
输入数M,应该以字符串的形式输入,方便后续的操作,首先将字符串转化成数字(注意16进制的特判),然后利用while循环,当利用加起来的数是否是回文数作为循环条件,初始状态就是输入的数a,循环中首先得到a的颠倒数b,然后再写一个加法函数,将a数和b数相加,因为本题还规定了自己输入n进制,所以我们加的时候需要符合n进制,其实跟十进制一样,就是把%10 /10中的10换成n,然后步数加一,如果步数已经超过30,就直接退出循环,否则直到它一直加到变成回文数为止。
代码:
#include <iostream>
#include <string>
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, q[1000001], l, w[1000001], ans;//q是转置成数字的数组
string s;
bool hw(int a[])//判断是否是回文数
{
int ln = l;//记录比较次数
int i = 1;//从两边向中间比较
int j = l;
while(ln--)
{
if(ln < l / 2)
{
break;
}
if(a[i] != a[j])
{
return false;
}
i++;
j--;
}
return true;
}
void intit()//把字符串转成数字
{
int num = 0;
for(int i = s.length() - 1; i >= 0; i--)
{
if(s[i] >= '0' && s[i] <= '9')
{
q[++num] = s[i] - '0';
}
else//16进制转法
{
q[++num] = s[i] - 'A' + 10;
}
}
}
void turn(int a[])//颠倒数
{
int j = 0;
for (int i = l; i >= 1; i--)//倒着存
{
w[++j] = a[i];
}
}
void add(int a[], int b[])
{
for(int i = 1;i <= l; i++)
{
a[i] += b[i];
a[i + 1] += a[i] / n;//进位,n进制就是除n
a[i] %= n;
}
if(a[l + 1] > 0)//因为这里的a是外面传进来的p数组,所以如果加起来超过了原来的长度,也就是进位了,那么长度也要跟着变,因为
//后续也要用到l,也就是要实时更新数p的长度l的值
{
l++;
}
}
int main()
{
cin >> n >> s;
intit();//将输入的字符串数组转换成数字
l = s.length();//得到字符串的长度
while(!hw(q))//如果当前数不是回文数,才要继续加下去
{
turn(q);//将q数颠倒
add(q, w);//将两个数加起来
ans++;//总步数加1
if(ans > 30)
{
break;
}
}
if(ans > 30)
{
printf("Impossible!");
}
else
{
printf("STEP=%d", ans);
}
return 0;
}