1. 进制转换
题目描述
请你编一程序实现两种不同进制之间的数据转换。
输入格式
共三行,
第一行是一个正整数,表示需要转换的数的进制n ( 2 ≤ n ≤ 16 ) ,
第二行是一个 n 进制数,若n > 10 则用大写字母A − F 表示数码10 − 15 ,并且该n 进制数对应的十进制的值不超过10^9,
第三行也是一个正整数,表示转换之后的数的进制m ( 2 ≤ m ≤ 16 )。
输出格式
一个正整数,表示转换之后的 m 进制数。
样例输入 #1
16
FF
2
样例输出 #1
11111111
思路:
题意:将 n进制的数 X 转化为m 进制的数。
先把n进制转换为十进制,十进制再转换为 m进制。即可!
n进制转换为十进制:
方法一:按权展开,按位相加。
起初给定的是一个 n进制的字符串 S,从后往前取出每一位字符,再乘以对应的权值,最后将每位的乘积结果相加。
从低位往高位递进,则权值也是不断地增大地。即从后往前取,每往左一位,就让权值乘以该进制的一个基数。最后记得求和!
// 将字符转为10进制的数字
int CharToInt(char ch)
{
if (isdigit(ch)) return ch - '0';
else return ch - 'A' + 10;
}
// 将n进制数转为10进制的数
void ToDecimal (int n, string s)
{
int res=0, base = 1;// 基从1开始,从低位到高位处理,每向左一位,基底的幂就高一次
for (int i=s.size()-1; i>=0; i--)
{
res += CharToInt(s[i])*base;
base *= n;
}
}
方法二:(我的)从高位向低位取,每次和乘以基数再加上每位上的值。
// 将n进制数转为10进制
int numNto10(int n, string str) {
int sum = 0;
for (int i = 0; i < str.size(); i++) {
sum = sum * n + CharToInt(str[i]); //和乘以基数再加上每位上的值
}
return sum;
}
十进制转换为 m进制:除 m 基数,从下往上取余法。
每次除 m 基数,保留的余数。但是得到的余数是自底向上的,这样就是逆序的,如果想要正常的顺序,可以:
方法一:用char数组接收每一个余数,并记录下数组的长度,然后逆序输出(栈同理);
// 数字转为字符
char IntToChar (int x)
{
if (x < 10) return x + '0';
else return x - 10 + 'A';
}
// 将数Num转为Newk进制数
void IntToChar(int Num, int Newk)
int cnt;
char ans[107];//转换后的数int cnt=0;
while (Num) {//10->newK(
ans[cnt++] = IntToChar (Num % Newk);
Num /= Newk;
}
for (int i = cnt - 1; i >= 0; i--)//用栈也行
cout << ans[i];
return 0;
}
方法二:(我的)用string接收每一个余数,但是不同的每次把结果加在结果字符串的前面,而不是后面,是str=c+str;
// 将数nums转为m进制数
string IntToChar(int nums, int m)
{
string res = "";
while (m)
{
int t = nums % m;
res = IntToChar(t) + res; //把结果加在结果字符串的前面
nums /= m;
}
return res;
}
方法三:用string接收,然后再用reverse函数,将结果逆置。
// 将十进制数转换为m进制数
string ToMBase(int m, int num)
{
string res = ""; // res存储结果字符串
while (num > 0) // 对十进制数进行短除法
{
int r = num % m; // 取得余数r
res += IntToChar(r); // 将余数对应的m进制字符放在字符串末尾
num /= m; // 进行下一次短除法
}
reverse(res.begin(), res.end()); // 将得到的结果颠倒过来
return res; // 返回转换后的m进制数字符串
}
总体代码:
#include<iostream>
#include<string>
using namespace std;
// 将n进制数转为10进制
int numNto10(int n, string str) {
int sum = 0;
for (int i = 0; i < str.size(); i++) {
int num; // 每一位表示的数字
if (str[i] >= '0' && str[i] <= '9') {
num = str[i] - '0';
} else { //对应的字母转为数字
num = str[i] - 'A' + 10;
}
sum = sum * n + num;
}
return sum;
}
// 由10进制数转为其他进制的数
string num10toN(int num, int n) {
string ans = "";
while (num > 0) {
int temp = num % n;
char c;
if (temp > 9) {
c = 'A' + temp - 10;
} else {
c = '0' + temp;
}
ans = c + ans;
num = num / n;
}
return ans;
}
int main() {
int n; // 原本是n进制数
cin >> n;
string str; // n进制数
cin >> str;
int m; // 要转换的进制数
cin >> m;
// 整体的思路:先将n进制数转为10进制,再由10进制数转为其他进制的数
int num = numNto10(n, str);
string ans = num10toN(num, m);
cout << ans;
return 0;
}
2. 三角形分类
题目描述
给出三条线段 a , b , c a,b,c a,b,c 的长度,均是不大于 10000 10000 10000 的正整数。打算把这三条线段拼成一个三角形,它可以是什么三角形呢?
- 如果三条线段不能组成一个三角形,输出
Not triangle
; - 如果是直角三角形,输出
Right triangle
; - 如果是锐角三角形,输出
Acute triangle
; - 如果是钝角三角形,输出
Obtuse triangle
; - 如果是等腰三角形,输出
Isosceles triangle
; - 如果是等边三角形,输出
Equilateral triangle
。
如果这个三角形符合以上多个条件,请按以上顺序分别输出,并用换行符隔开。
输入格式
输入 3 个整数 a a a、 b b b 和 c c c。
输出格式
输出若干行判定字符串。
样例 #1
样例输入 #1
3 3 3
样例输出 #1
Acute triangle
Isosceles triangle
Equilateral triangle
提示
当两短边的平方和大于一长边的平方,说明是锐角三角形。
当两短边的平方和等于一长边的平方,说明是直角三角形。
当两短边的平方和小于一长边的平方,说明是钝角三角形。
注意事项:
三条边大小的比较
建议直接用数组接收,然后用sort函数排序,三条边从大到小就是arr[0], arr[1], arr[2],然后就用这三个来比较。
if-else之间的嵌套关系,什么时候用if-else,什么时候只用if
直角三角形,锐角三角形与钝角三角形,以上三者只可能取其一所以用 if / else if/ else
等腰三角形和等边三角形只有是或者不是;可以同时是,也可以同时不是//互不干扰,写两个if
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
// 先排序,然后找到两个短边以及一个长边
int edges[3];
for (int i = 0; i < 3; i++) {
cin >> edges[i];
}
sort(edges, edges + 3);
int a = edges[0], b = edges[1], c = edges[2];
//再判断是什么类型的三角形
if (a + b <= c) { //不能组成一个三角形
cout << "Not triangle" << endl;
} else { //判断是什么类型的三角形
if (a * a + b * b == c * c) { //直角三角形
cout << "Right triangle" << endl;
} else if (a * a + b * b > c * c) { //锐角三角形
cout << "Acute triangle" << endl;
} else { // a * a + b * b < c * c) 钝角三角形
cout << "Obtuse triangle" << endl;
}
//以上三者只可能取其一所以用 if / else if/ else
//等腰三角形和等边三角形只有是或者不是;可以同时是,也可以同时不是//互不干扰,写两个if
if (a == b) { //等腰三角形,两个短边相等
cout << "Isosceles triangle" << endl;
}
if (a == b && b == c) { //等边三角形,三边相等
cout << "Equilateral triangle" << endl;
}
}
return 0;
}
3. 小鱼的游泳时间
题目描述
伦敦奥运会要到了,小鱼在拼命练习游泳准备参加游泳比赛,可怜的小鱼并不知道鱼类是不能参加人类的奥运会的。
这一天,小鱼给自己的游泳时间做了精确的计时(本题中的计时都按 24 24 24 小时制计算),它发现自己从 a a a 时 b b b 分一直游泳到当天的 c c c 时 d d d 分,请你帮小鱼计算一下,它这天一共游了多少时间呢?
小鱼游的好辛苦呀,你可不要算错了哦。
输入格式
一行内输入四个整数,以空格隔开,分别表示题目中的 a , b , c , d a, b, c, d a,b,c,d。
输出格式
一行内输出两个整数 e e e 和 f f f,用空格间隔,依次表示小鱼这天一共游了多少小时多少分钟。其中表示分钟的整数 f f f 应该小于 60 60 60。
样例 #1
样例输入 #1
12 50 19 10
样例输出 #1
6 20
提示
对于全部测试数据, 0 ≤ a , c ≤ 24 0\le a,c \le 24 0≤a,c≤24, 0 ≤ b , d ≤ 60 0\le b,d \le 60 0≤b,d≤60,且结束时间一定晚于开始时间。
方法一:
两个时间先转为分钟,然后再减,再转为几小时几分钟
#include<iostream>
using namespace std;
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
// 两个时间先转为分钟,然后再减,再转为几小时几分钟
int time1 = a * 60 + b;
int time2 = c * 60 + d;
int lastTime = time2 - time1;
int e = lastTime / 60, f = lastTime % 60; // 存放最后的结果
cout << e << " " << f << endl;
return 0;
}
方法二:
计算两个时间相差多少分钟,再转为几小时几分钟
#include<iostream>
using namespace std;
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
int lastTime = (c - a) * 60 + d - b; //计算两个时间相差多少分钟
int e = lastTime / 60, f = lastTime % 60; // 存放最后的结果
cout << e << " " << f << endl;
return 0;
}
4. 数字反转
题目描述
输入一个不小于 100 100 100 且小于 1000 1000 1000,同时包括小数点后一位的一个浮点数,例如 123.4 123.4 123.4 ,要求把这个数字翻转过来,变成 4.321 4.321 4.321 并输出。
输入格式
一行一个浮点数
输出格式
一行一个浮点数
样例 #1
样例输入 #1
123.4
样例输出 #1
4.321
思路:
这道题的精髓在于,破题,使用技巧,不要生硬的做模拟题
逆序输出字符串即可QAQ
代码:
#include<iostream>
using namespace std;
int main() {
string str;//逆序输出字符串即可cin>>str;
for (int i = str.size() - 1; i >= 0; i--)
cout << str[i];
cout << endl;
return 0;
}
我的方法:
先把浮点数*10变成整数,然后反转每一位数字,再将最后结果除以1000即可
代码:
#include<iostream>
using namespace std;
// 先把浮点数*10变成整数,然后反转每一位数字,再将最后结果除以1000即可
int main() {
double numOrign;
cin >> numOrign;
int num = numOrign * 10;
// 反转
int sum = 0;
while (num > 0) {
int temp = num % 10;
sum = sum * 10 + temp;
num = num / 10;
}
double ans = 1.0 * sum / 1000;
cout << ans << endl;
return 0;
}
5. 数字反转(升级版)
题目描述
给定一个数,请将该数各个位上数字反转得到一个新数。
这次与 NOIp2011 普及组第一题不同的是:这个数可以是小数,分数,百分数,整数。
-
整数反转是将所有数位对调。
-
小数反转是把整数部分的数反转,再将小数部分的数反转,不交换整数部分与小数部分。
-
分数反转是把分母的数反转,再把分子的数反转,不交换分子与分母。
-
百分数的分子一定是整数,百分数只改变数字部分。
注意事项:
是否考虑全?
小数部分后置0是否消除 1.00200
如果全0,你是否输出多个。
是否考虑小数部分只有0
提升代码凝练能力
主要是抽象出公共的功能,封装成函数以重复利用
解题思路:
主要思路是,读如字符串后,判断字符串中是属于哪一种类型的数字。
一共有四种情况:正整数、正实数、正分数、正百分数。
- 如果是正整数,先反转,再去掉前导零,输出。
- 如果是百分数,先按正整数方案做,最后输出一个百分号。
- 如果是分数,以分数线为分界,将字符串分为左右两部分,分别进行反转,前一部分和后一部分都要去掉前导0。
- 如果是小数,以小数点为分界,将字符串分为左右两部分,分别进行反转,前一部分去掉前导0,后一部分要去掉后导0。
综上,我们有以下几个问题需要解决:
-
如何反转字符串?
用 #include<algorithm> 中的 reverse(str.begin(), str.end())
-
如何去掉前导零?
先遍历一遍字符串,看看有多少前导零,然后 用substr截取不含0的子串 即可。
-
如何提取出子字符串?
std::string::substr()。
但要注意,如果数字本身就是零,按照上面的做法会得到空串。所以需要特判。
代码:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
// 去掉前置:找到开头最后一个不为0的位置,并删掉前面的0
string deleteHead0(string str) {
int i = 0;
while (i < str.size() && str[i] == '0') {
i++;
}
if (i == str.size()) { // 说明全是0
return "0";
}
return str.substr(i, str.size());
}
// 去掉后置0:从后往前找到最后一个不为0的位置,并删掉后面的0
string deleteTail0(string str) {
int i = str.size() - 1;
while (i >= 0 && str[i] == '0') {
i--;
}
if (i == -1) { // 说明全是0
return "0";
}
return str.substr(0, i + 1);
}
// 对于分数和小数的反转
string reverse2(string str, int index, char c) {
// 反转前后两部分
string str1 = str.substr(0, index);
string str2 = str.substr(index + 1, str.size() - 1);
reverse(str1.begin(), str1.end());
reverse(str2.begin(), str2.end());
string ans = "";
if (c == '/') {
// 如果是分数,去掉前置0
ans = ans + deleteHead0(str1) + "/" + deleteHead0(str2);
} else {
// 如果是小数,整数部分去掉前置0,小数部分去掉后置0
ans = ans + deleteHead0(str1) + "." + deleteTail0(str2);
}
return ans;
}
int main() {
// 首先,输入的位数有20位,一定不是数字,而是作为字符串进行输入的
string str;
cin >> str;
// 然后判断是哪种类型的数字
// 百分数
if (str[str.size() - 1] == '%') {
str = str.substr(0, str.size() - 1);
reverse(str.begin(), str.end());
cout << deleteHead0(str) << "%" << endl;
return 0;
}
int index;
string str1, str2;
// 分数
if ((index = str.find('/')) != string::npos) {
cout << reverse2(str, index, '/') << endl;
return 0;
} else if ((index = str.find('.')) != string::npos) {
cout << reverse2(str, index, '.') << endl;
return 0;
}
// 最后剩下的一种情况就是整数,反转,然后去掉前置0
reverse(str.begin(), str.end());
cout << deleteHead0(str) << endl;
return 0;
}
6. 统计单词数
题目描述
一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。
现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。
注意:匹配单词时,不区分大小写,但要求完全匹配,即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同(参见样例 1),如果给定单词仅是文章中某一单词的一部分则不算匹配(参见样例 2)。
输入格式
共 2 2 2 行。
第 1 1 1 行为一个字符串,其中只含字母,表示给定单词;
第 2 2 2 行为一个字符串,其中只可能包含字母和空格,表示给定的文章。
输出格式
一行,如果在文章中找到给定单词则输出两个整数,两个整数之间用一个空格隔开,分别是单词在文章中出现的次数和第一次出现的位置(即在文章中第一次出现时,单词首字母在文章中的位置,位置从 0 0 0 开始);如果单词在文章中没有出现,则直接输出一个整数 − 1 -1 −1。
注意:空格占一个字母位
样例 #1
样例输入 #1
To
to be or not to be is a question
样例输出 #1
2 0
样例 #2
样例输入 #2
to
Did the Ottoman Empire lose its power at that time
样例输出 #2
-1
方法一:用find函数来查找
代码:
#include <iostream>
#include <string>
//命名空间
using namespace std;
int main() {
//定义两个字符串
string key, str;
//用string库,调用getline, 直接读入一整行
getline(cin, key);
getline(cin, str);
//转换大小写,可以都转换为大写,或者小写
for (int i = 0; i < key.length(); ++i) {
key[i] = tolower(key[i]);
}
for (int i = 0; i < str.length(); ++i) {
str[i] = tolower(str[i]);
}
//因为连起来的不算,所以要在前后加几个空格,一定要是同样多的,同量减同量,等于同量
key = ' ' + key + ' ';
str = ' ' + str + ' ';
//先看看会不会找不到,用a.find()和string::npos
if (str.find(key) == string::npos) {
cout << -1 << endl;
return 0;
}
//如果找得到
//虽然前后加了空格,但是因为key前后也加了空格,
// 所以最后找到的位置跟以前没加空格的位置是一样的。
int firstPos = str.find(key);
int index = str.find(key), count = 0;//计数器初始化为0
// 找到之后,次数加一,继续往后面的字符串中找
while (index != string::npos) {
++count;//计数器
index = str.find(key, index + 1);
}
cout << count << " " << firstPos << endl;//输出第一个和总共有几个
return 0;
}
方法二:普通的查找
7.数字直角三角形
题目描述
给出 n n n,请输出一个直角边长度是 n n n 的数字直角三角形。所有数字都是 2 2 2 位组成的,如果没有 2 2 2 位则加上前导 0 0 0。
输入格式
输入一个正整数 n n n。
输出格式
输出如题目要求的数字直角三角形。
样例 #1
样例输入 #1
5
样例输出 #1
0102030405
06070809
101112
1314
15
输出两位数,不足两位数高位补零
方法一:C语言的printf(“%02d”, 变量名);
其中2表示占两位,0表示不足两位,高位补零
方法二:C++的setw( )函数与setfill( )函数
setw():设置域宽
cout<<setw(2)<<'a'<<endl;
// 输出:(空格)a
//(域宽设置长度为2,a占1个长度,所以前面输出1个空格,此函数只对紧跟后面的输出生效)
setfill():设置填充符号
设置setw()在填充域宽时的符号,默认为空格,搭配setw使用 如
cout<<setfill('0')<<setw(2)<<'a';
//输出 0a
因此这道题在处理输出数字时就可以这样处理:
cout<<setfill('0')<<setw(2)<<num;//num为要输出的数
代码:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n;
cin >> n;
int num = 1; // 数字从1开始输出
// 数字三角形是n行n列
for (int i = 1; i <= n; i++) {
for (int j = n; j >= i; j--) {
cout << setfill('0') << setw(2) << num;
num++;
}
cout << endl;
}
}
8. 全排列问题
题目描述
按照字典序输出自然数 1 1 1 到 n n n 所有不重复的排列,即 n n n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
输入格式
一个整数 n n n。(1≤n≤9)
输出格式
由 1 ∼ n 1 \sim n 1∼n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 5 5 5 个场宽。
样例 #1
样例输入 #1
3
样例输出 #1
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
方法一:排列树(虽然结果对,但是顺序不对,通过不了)
这个本来是回溯法的排列树,但是如果按照排列树写的话,因为有交换,所以最后输出的数字组合不是字典序,通过不了。
代码:
#include<iostream>
#include <iomanip>
using namespace std;
int n;
int ans[10];
// 回溯法
void dfs(int t) {
if (t > n) {
for (int i = 1; i <= n; i++) {
cout << setw(5) << ans[i];
}
cout << endl;
return;
}
// 选第t个位置上的数,前t-1个已经选好了
for (int i = t; i <= n; i++) {
swap(ans[i], ans[t]);
dfs(t + 1);
swap(ans[i], ans[t]);
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
ans[i] = i;
}
dfs(1);
return 0;
}
运行结果是:
但是这个顺序不对QAQ
方法二:子集树的+剪枝
题中要求按照字典序输出,所以选择用子集树的方式+剪枝来做。
代码:
#include<iostream>
#include <iomanip>
using namespace std;
int n;
int ans[10];
int vis[10];
// 回溯法
void dfs(int t) {
if (t > n) {
for (int i = 1; i <= n; i++) {
cout << setw(5) << ans[i];
}
cout << endl;
return;
}
// 选第t个位置上的数,从1-N中选,但是不能有重复
for (int i = 1; i <= n; i++) {
if (!vis[i]) { // 剪枝
ans[t] = i;
vis[i] = 1;
dfs(t + 1);
vis[i] = 0; // 回溯
}
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
9. 逆序对
题目描述
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj 且 i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
输入格式
第一行,一个数 n n n,表示序列中有 n n n个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109。
输出格式
输出序列中逆序对的数目。
样例 #1
样例输入 #1
6
5 4 2 6 3 1
样例输出 #1
11
提示
对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n≤2500
对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n≤4×104。
对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n≤5×105
请使用较快的输入输出
应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧
解题思路:
分治法,在归并排序的过程中计算逆序对。
需要注意的点是最后结果的类型,因为数据 n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n≤5×105,最坏情况下,所有数据逆序,有O(n^2),大概是1e10,超过了int的长度,所以要用long long来表示。
代码:
#include<iostream>
using namespace std;
const int MaxNum = 5e5 + 3;
int arr[MaxNum];
int tmpArr[MaxNum];
long long ans; // 记录逆序对的个数,易错!
void merge(int arr[], int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
tmpArr[k++] = arr[i++];
} else {
ans += (mid - i + 1);
tmpArr[k++] = arr[j++];
}
}
while (i <= mid) {
tmpArr[k++] = arr[i++];
}
while (j <= right) {
tmpArr[k++] = arr[j++];
}
for (i = left; i <= right; i++) {
arr[i] = tmpArr[i];
}
}
// 逆序对:在归并排序的过程中求逆序对
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// merge两部分
merge(arr, left, mid, right);
}
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
mergeSort(arr, 0, n - 1);
cout << ans << endl;
}