总结
这次竞赛放在工作日的晚上,下班回来挺困了,做了两题都快睡着了,状态不佳,做的不是很理想。
总的来说,这次竞赛的 T 2 T2 T2考察贪心的题目还是挺不错的,其他题目更多的是考察阅读理解。
题目列表
1.陶陶摘苹果
题目描述
陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 10 个苹果。苹果成熟的时候,陶陶就会跑去摘苹果。陶陶有个 30 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩到板凳上再试试。 现在已知 10 个苹果到地面的高度,以及陶 陶把手伸直的时候能够达到的最大高度,请帮陶陶算一下她能够摘到的苹果的数目。假设她碰到苹果,苹果就会掉下来。
分析
签到题,能够触及的最大高度是 m + 30 m+30 m+30,遍历下苹果树的高度,不超过最大高度,都可以摘到。
代码
#include <iostream>
#include <string>
int solution(int arr[10], int m) {
int t = m + 30;
int res = 0;
for(int i = 0; i < 10; i++) {
if(arr[i] <= t) res++;
}
return res;
}
int main() {
int arr[10];
int max_tourch_height;
for (int i = 0; i < 10; i++) {
std::cin>>arr[i];
}
std::cin>>max_tourch_height;
int result = solution(arr, max_tourch_height);
std::cout<<result<<std::endl;
return 0;
}
2.硬币的面值
题目描述
小A有n枚硬币,现在要买一样不超过m元的商品,他不想被找零,同时又不想带太多的硬币,且硬币可以重复,现在已知这n枚硬币的价值,请问最少需要多少硬币就能组合成所有可能的价格?
输入描述:
第一行两个数:n、m。
下一行,共n个数字,表示硬币的面值。
输出描述:
一行一个数,表示最少需要多少硬币。如果无解请输出“No answer!!!”
输入样例:
5 31
1 2 8 4 16
输出样例:
5
分析
本题考察贪心,如果没有做过类似的题目,现场想出来还是挺不容易的。要组成1到m之间所有可能的价格,最基本的条件就是带上1元硬币,只有这样才能购买1元的商品而不被找零。如果小A有足够多的1元硬币,也是可以购买任意价格的商品的,所以有没有1元硬币就是判断问题是否有解的依据。
举个例子:用1 2 5 10四种面值的硬币组成1到20种所有的价格,要求携带的硬币数最少。
- 1是必须携带的,为了购买1元的商品。
- 如果要购买1到2元的商品,可以选择带2个1,也可以选择带1个1和1个2,我们选择后者,因为带2个1只能组成1到2的价格,而带上1个1和1个2可以组成1到3的价格,我们希望组成的价格范围越大越好,所以选择带上2。
- 现在1到3的价格都可以组成了,为了组成4,我们只能再带一个1和一个2,还是贪心的选择了2,这样就可以组成1到5之间所有的价格了。
- 再带上一个5,我们就能组成1到10之间所有的价格。
- 最后带上10,就可以组成1到20之间所有的价格。
经过上面的分析,我们发现只需要携带5枚硬币就可以组成1到20之间所有的价格了。
对于一般的情况,使用 1 , a 2 , a 3 , . . . , a n 1,a_2,a_3,...,a_n 1,a2,a3,...,an来组成1到m的价格。假设我们使用前 i − 1 i-1 i−1个硬币组成了1到S之间所有的价格。
- 如果 a i − 1 > S a_i - 1> S ai−1>S,那么需要更多的硬币才能表示前 a i − 1 ai -1 ai−1的价格,我们选择不断的加上 a i − 1 a_{i-1} ai−1,直至S不小于 a i − 1 ai -1 ai−1。
- 如果 a i − 1 < = S a_i - 1<= S ai−1<=S,那么可以先继续考虑下一个更大面值的硬币了
为什么以 a i − 1 a_i -1 ai−1为分界点呢?因为只有S不小于 a i − 1 a_i-1 ai−1,再使用 a i a_i ai才能有效的扩展S。比如S = 3,直接加上5面值的硬币,那么4就不能表示出来了。所以本题的贪心策略也就是:自小到大排序硬币的面值,去掉超过m的面值,因为用不上。然后遍历面值数组,遍历到 a i a_i ai时,首先判断 a i − 1 a_i-1 ai−1与 S S S的大小关系,如果S较小,就不断的对S累加上 a i − 1 a_{i-1} ai−1,直至S不小于 a i − 1 ai-1 ai−1。
由于遍历的过程就是不断的把 S S S往 a i − 1 a_i-1 ai−1上累加的过程,所以可以设置最后一个硬币为哨兵节点,面值为m + 1,这样遍历到最后一个硬币面值, S S S的值就可以保证不小于 m m m了。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105;
int a[N];
int main() {
int m, n;
cin>>n>>m;
for(int i = 0; i< n;i++) cin>>a[i];
sort(a, a + n);
if (a[0] != 1) cout<<"No answer!!!"<<endl;
else {
while(a[n - 1] >= m) n--;
a[n] = m + 1;
int s = 0, res = 0;
for(int i = 1;i <= n;i++) {
if(s >= m) break;
if (s < a[i] - 1) {
int k = (a[i] - s + a[i-1] - 2) / a[i-1];
res += k;
s += k * a[i - 1];
}
}
cout<<res<<endl;
}
return 0;
}
3. 公司新表
题目描述
公司里为了凸显公司的特性。
安装了一个n进制表。
已知新的表的时间是”H:M”。
时间合法的定义为H<=23 && M<=59。
时间有多少种进制定义的方式,依次打印出来。
如果有无数种解输出”-1”,不存在输出”0”。
输入描述:
输入一行字符串a:b形式。
输出描述:
输出答案。
输入样例:
11:20
输出样例:
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
分析
这题题目描述不够清楚,但是有用例解释还是可以理解的,给定一个字符串,如果用k进制解释这个字符串后的数字大小在时钟范围内,就是合法的。简单的模拟题,由于比赛时状态不佳,只通过了六成用例,赛后重新写了下。
首先要考虑的是字符串的解析,尽管题目用例给的是像 11 : 20 11:20 11:20之类的字符串,但是是否存在 1 : 2 1:2 1:2,甚至是 122 : 234 122:234 122:234类似的字符串,也不能保证。字符串中出现的最大字符转化为数字后是t,那么至少要t + 1进制才能表示该字符串。
如果字符串是用 i i i进制表示的,将冒号前后的字符串转化为十进制数字后得到 x : y x:y x:y,只要 x < 24 , y < 60 x<24,y<60 x<24,y<60,那么这个进制表示就是合法的。注意如果使用 i i i进制表示是合法的,那么使用 i − 1 i-1 i−1进制表示的数值只会更小,也是合法的。像 01 : 01 01:01 01:01这类只有一位的表示,不管是几进制表示得到的数都很小,因为最低位的基数都是1。我们可以从 t + 1 t + 1 t+1枚举到60,如果60进制都是合法的,更高进制的表示一定也是合法的,因为如果时间有两位表示,60进制的两位数是不小于60的。60进制合法就表示有无数种解。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int get(char c) {
if (c >= '0' && c <= '9') return c - '0';
else return c - 'A' + 10;
}
std::vector<int> solution(std::string m) {
std::vector<int> res;
int t = 0;
int n = m.size();
for(int i = 0; i < n; i++) {
if(m[i] == ':') continue;
t = max(t, get(m[i]));
}
for(int i = t + 1; i <= 60; i++) {
int b[2] = {0, 0};
int p = 0;
for(int j = 0;j < n;j++) {
if(m[j] == ':') {
p++;
continue;
}
int q = get(m[j]);
b[p] = b[p] * i + q;
}
if(b[0] >= 24 || b[1] >= 60) break;
else res.push_back(i);
}
if (!res.size()) {
return {0};
} else if(res.back() == 60) return {-1};
return res;
}
int main() {
std::string m;
getline(std::cin, m);;
std::vector<int> result = solution(m);
for(auto it=result.begin(); it!=result.end(); ++it) {
std::cout<<*it<<" ";
}
std::cout<<std::endl;
return 0;
}
4.题目名称:小豚鼠排排坐
题目描述
小艺酱买了一个由一排排格子组成的小房子n*m,她想让k个小豚鼠每个小豚鼠都有自己的格子。但是为了不浪费空间,她想要最边角的一圈2*(n+m-2)每行每列的格子都有一个小豚鼠居住。
具体来说,假设这k只小豚鼠的格子坐标为(x1, y1), (x2, y2),…,(xk, yk),则需要满足存在1<a,b,c,d<=k,使得xa =1, xb = n, yc = 1, yd = m (a, b, c, d可以重复).
小艺酱想知道自己有多少种方案安排小豚鼠。
输入描述:
输入整数n,m,k。(1<=n,m<=5,0<=k<=n*m)
输出描述:
输出方案数。
输入样例:
3 3 2
输出样例:
2
分析
本题给的数据范围很小,所以可以直接暴搜,甚至不用优化。要注意的是小豚鼠可以住在中间的格子,不是非要住在边界格子。而且并不是每行每列都需要有小豚鼠居住,只要最外围的行和列至少住了一只就可以了。dfs时可以使用状态压缩表示和判断状态,具体实现见代码。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int a[30],b[30];
int ans = 0,cnt = 0;
int n,m,k;
void dfs(int u,int c,int s,int t) {
if(c == k) {
if((s & 1) && (t & 1)) {
if((s >> (n - 1) & 1) && (t >> (m-1) & 1)) ans++;
return;
}
}
if(u >= cnt) return;
//放小豚鼠
dfs(u+1,c+1,s | (1 << a[u]),t | (1 << b[u]));
//不放小豚鼠
dfs(u+1,c,s,t);
}
int main() {
cin>>n>>m>>k;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
a[cnt] = i;
b[cnt++] = j;
}
}
dfs(0, 0, 0 , 0);
cout<<ans<<endl;
return 0;
}