西南财经大学 奇点工作室 程序设计部暑期训练营第二周 题目分析
A 小H和迷宫
题目分析:一道比较简单的题目,但是比较考察初学者的细节理解。3种药水全排列仅有3*2*1=6种情况,使用next_permutation枚举即可。需要注意的是,我们要注意类型转换造成的精度问题,所以数据范围要开为long long,并采用先乘后除的思想即可通过(方法不唯一)。
#include <bits/stdc++.h> using namespace std; int main(){ long long n; long long num[3]; scanf("%lld%lld%lld%lld",&n,&num[0],&num[1],&num[2]); sort(num,num+3); //先排序,因为next_permutation是从第一个字典序大的开始,否则会少情况 long long m=-1; //记录所有答案的最大值 do{ long long sum=0; //计算每一种情况的答案 sum=n*(100-num[0])/100; //剩余血量 sum=sum*(100-num[1])/100; sum=sum*(100-num[2])/100; sum=n-sum; m=max(m,sum); }while(next_permutation(num,num+3)); printf("%lld",m); return 0; }其实,我们不难发现,本题目可以采用贪心的思想来解决,我们可以通过数学公式证明先使用厉害的药水比其他情况更优。所以我们可以直接排序然后输出结果。
#include<bits/stdc++.h> using namespace std; int main() { long long n; double a[4]; cin >> n >> a[1] >> a[2] >> a[3]; long long nn = n; //直接计算答案 sort(a + 1,a + 4); nn = nn - nn*a[3]/100; nn = nn - nn*a[2]/100; nn = nn - nn*a[1]/100; printf("%lld\n",n - nn); return 0; }
B 脸盆大哥的木桶
题目分析:因为底面积是固定的,并且总的盛水量肯定是由最短的那个木板决定的,我们考虑贪心,就是先排序,从高到低每次都将k个放在一组,此时答案最优。下面是简单的证明:我们考虑贪心时的分组a和b,其中各有k个元素,此时
我们把a中的最小值记作min_a,b中的记作min_b。很显然,两个分组下木桶可以装的水只由min_a,min_b决定。此时我们考虑其他策略,任选a组中一个元素i,b组一个元素j,我们进行交换,由于我们经过排序,一定有i>=min_a>=j>=min_b。交换过后,a组中的最小值就会变为j,b组中最小值不变。由于j<=min_a,a组中装的水会减少(特殊情况没有影响),所以我们采取的策略最优。
我们把a中的最小值记作min_a,b中的记作min_b。很显然,两个分组下木桶可以装的水只由min_a,min_b决定。此时我们考虑其他策略,任选a组中一个元素i,b组一个元素j,我们进行交换,由于我们经过排序,一定有i>=min_a>=j>=min_b。交换过后,a组中的最小值就会变为j,b组中最小值不变。由于j<=min_a,a组中装的水会减少(特殊情况没有影响),所以我们采取的策略最优。
#include<bits/stdc++.h> using namespace std; int main() { int t,n,k,s,a[1001]; cin >> t; while(t--){ cin >> n >> k >> s; memset(a,0,sizeof(a)); //数组的初始化 for(int i = 0;i < n;i ++){ cin >> a[i]; } sort(a,a+n); int sum=0; //记录答案 for(int i = n-k;i >= 0;i -= k){ //反向遍历 sum += a[i]*s; } cout << sum << endl; } return 0; }
C 竞赛技巧
题目分析:按照题意排序即可,方法多种多样,可以使用结构体,可以使用vector,可以使用pair<int,pair<int,int>>等等等,这里给出使用vector的程序。
#include<bits/stdc++.h> using namespace std; vector<vector<int>> t; //初始化 int cmp(vector<int> &a,vector<int> &b){ //定义两个vector的比较函数,先按第一位,再按第二问,最后按第三位 if(a[0] == b[0]){ if(a[1] == b[1])return a[2] < b[2]; else return a[1] < b[1]; } else return a[0] < b[0]; } int main(){ int n; cin >> n; for(int i = 0;i < n;i ++){ int a,b,c; cin >> a >> b >> c; int time[3] = {a,b,c}; vector<int> p(time,time+3); //利用数组初始化vetcor t.push_back(p); //存入容器中 } sort(t.begin(),t.end(),cmp); for(auto p:t){ cout << p[0] << " " << p[1] << " " << p[2] << endl; } return 0; }
总结:本题是一道基础的排序题目,考察大家对匿名函数的掌握,通过率不足50%,主要原因有:有很多同学把输入的字符串进行排序,这是不科学的,因为对于字符串“9 10 10”它是大于“10 10 10”,这会导致结果出错;还有很多同学计算总时间进行排序,然后再拆分,这个思路是正确的,但是表达式写错了()
D 凌波微步
题目分析:由于只能往高处走,方向也无所谓,所以我们只需要记录里面有多少不一样的值就行,不难想到用集合set。
#include<bits/stdc++.h> //这个题目我认为不需要注释了 using namespace std; int main() { int t,n,x; cin>>t; while(t--) { set<int>a; cin>>n; for(int i=0;i<n;i++) { cin>>x; a.insert(x); } cout<<a.size()<<endl; } return 0; }
总结:本题通过率很低,主要原因是大家一开始没有读懂题,然后有部分同学对set的用法不熟练,希望大家加强对STL的敏感。
此外,我们可以思考两个问题
1.如果在此题基础上,要求你输出路径,应该如何实现?
2.如果在此题基础上,要求只能从第一个开始,方向只能向右,应该如何实现?
欢迎大家讨论交流
E 老子的全排列呢
题目分析:直接套函数全排列即可,不过也推荐大家去尝试一下递归的写法。
#include <bits/stdc++.h> //这个题目我认为不需要注释了 using namespace std; int main() { vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8}; do { for (int num : nums) { cout << num << " "; } cout << endl; } while (next_permutation(nums.begin(), nums.end())); return 0; }
本题大家出错主要在不清楚next_permutation是从第一个比他字典序大的开始,如果使用while循环,会少输出它本身,所以我们这里建议使用do while循环。
F 简单的数据结构
题目分析:直接使用vector等容器模拟即可。
#include<bits/stdc++.h> //这个题目我认为不需要注释了 using namespace std; int main(){ int n,m;cin >> n >> m; vector<int> v; for(int i = 0; i < m;i++){ int op;cin >> op; if(op == 1){ int x;cin >> x; v.insert(v.begin(),x); } else if(op == 2) v.erase(v.begin()); else if(op == 3) { int x;cin >> x; v.push_back(x); } else if(op == 4) v.pop_back(); else if(op == 5) reverse(v.begin(), v.end()); else if(op == 6){ cout << v.size() << endl; for(auto x:v) cout << x << " "; cout << endl; } else sort(v.begin(),v.end()); } return 0; }
G 投票统计
题目分析:注意到编号ai是非连续的,且范围为1-1e9,我们考虑使用map来存储票数,由于map是按照键进行排序的,所以我们可以在统计时顺便计算最大值,然后遍历一遍判断是否所有的票数一致,如果一致,则输出-1,否则,我们遍历找出票数等于最大值的编号。
#include <bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; map<long long, long long> mp; long long maxx = 0; //记录最大票数 for (int i = 0; i < n; i++) { long long x; cin >> x; mp[x]++; maxx = max(maxx,mp[x]); } int cnt = 0; //记录有多少个票数等于最大票数,即去看题目中的特殊情况是否满足 for (auto x : mp) if(x.second == maxx) cnt ++; if(cnt == mp.size()) cout << -1 << endl; //若都是一个票数,就输出-1 else{ cout << cnt << endl; for (auto x : mp) if(x.second == maxx) cout << x.first << " "; //遍历一次,找票数等于最大值的下标并输出 cout << endl; } } int main() { int t; cin >> t; while (t--) solve(); return 0; }
H 进制转换
题目分析:题意很简单,但需要注意s最大可以达到36的200次,显然是非常巨大的,即使是long long也记录不了,所以不能采取先转换为10进制再转为为目标进制的做法。这里给出一种比较经典的基于短除法的方法,类似于目标进制为10的短除法,这里的目标进制为k。短除法,就是把每一位(这里指的每一位是指个位十位之类的)除以要转换的进制的余数在乘以当前进制的值加到下一位去,当前位的值就为商,然后这样一直进行到最后一位(也就是个位)个位在对所须转换的进制在取模,那么这个模就是转换后的结果。多次重复,直到最后一位为0,从后往前看就是答案。以以八进制742转换为16进制1E2为例,首先需要将八进制数742倒置为247,由number数组按位保存,然后从后往前每一次循环做一次短除法,其中每个位置处理前都是上一位的余数乘上进制(这里进制为八)加上这个位置上的数,然后将当前位置对k进制的余数作为k进制的数(这里k为16),同时更新当前位置上的数为其除以k的商,不断重复直到s进制中的数全部为零。数学证明不在此赘述,毕竟我们不是数竞,一些进制转换算法的原理与证明 - 知乎 (zhihu.com)讲的比较清楚,大家可以尝试手推一下(不会推无所谓,还是那句话,毕竟我们不是数竞,会用就行)。
#include <bits/stdc++.h> using namespace std; int main() { string n; cin >> n; int s, k; cin >> s >> k; //把数字存到vector里面,字母数字分别处理 vector<int> v; for (int i = n.size() - 1; i >= 0; i--) { if (n[i] >= '0' && n[i] <= '9') v.push_back(n[i] - '0'); else v.push_back(n[i] - 'a' + 10); } //短除法模板 stack<int> st; while (!v.empty()) { int t = 0; for (int i = v.size() - 1; i >= 0; i--) { v[i] += t * s; t = v[i] % k; v[i] /= k; } st.push(t); while (!v.empty() && v.back() == 0) v.pop_back(); } //输出结果,字母数字分别处理 while (!st.empty()) { int num = st.top(); if (num <= 9) cout << num; else cout << (char)(num - 10 + 'a'); st.pop(); } return 0; }
总结:这道题目如果小学老师没讲短除法(的应用)然后自己手推公式确实比较难,这里推荐用栈,比其他的略快一点,但其他的也可以通过。
I 牛牛学括号
题目分析:这是一个比较简单的贪心问题,对于每个右括号,它可以选择它左边任何一个左括号匹配,所以我们直接记录左括号数目即可。
#include<bits/stdc++.h> using namespace std; int main(){ string s; cin >> s; int left = 0; //记录左括号的数目 long long mod = 1e9+7; long long ans = 1; //初始化结果 for(int i = 0;i < s.length();i ++){ if(s[i]=='(') left ++; //如果是左括号,则可选的情况加1 else{ ans = ans * left % mod; //如果是右括号,就在左括号里面随便选一个出来,然后左括号数目减1 left --; } } cout << ans << endl; return 0; }
最后总结
本周主题为C++STL和STL算法,但是STL真的太泛了,所以只能从牛客网找最几道算是基础的题目给大家练练手。而且不难发现,STL的使用是带有个体差异性的,有的人喜欢用数组,有的人喜欢用vector,有的人喜欢直接算答案,但其实大差不差,这只是大家解决题目的工具。工具的选择是非常重要的,比如map,set都有非常棒的应用场景,我们要做好选择,才能事半功倍。题目总体难度比上周略大,但主要难在综合性比较高,细节比较多,导致大家通过率不高,但主要的思路还是不难想的(除了H,需要一定数学能力)。大家不要慌张害怕,代码能力需要慢慢练习,祝大家第三周愉快,加油!