WHUT第二周训练整理
写在前面的话:我的能力也有限,错误是在所难免的!因此如发现错误还请指出一同学习!
索引
一、简单套路题:01、03、05、09、12、16、22
二、思维题:02、13、18、20
三、模拟题:04、07、08、11、15、17、19、21、23、24、25、26
四、数论题:06、10、14
本题解报告大部分使用的是C++语言,方便调用STL库以及相关容器。
一、简单套路题
1001:计算两个正整数的最小公倍数,即lcm。
分析:先求出两个数的最大公约数gcd,然后代入公式 a ⋅ b g c d ( a , b ) \frac{a\cdot b}{gcd(a, b)} gcd(a,b)a⋅b 即可。
Code:
// 这里求gcd直接调用algorithm库里的__gcd()了,手写也很简单
//int gcd(int a, int b){
// return b == 0 ? a : gcd(b, a%b);
//}
int main()
{
int a, b;
while (cin >> a >> b)
{
int d = __gcd(a, b);
cout << a * b / d << endl;
}
return 0;
}
1003:二分背景下猜数字n次可以猜到的最大数。
分析:在最差的情况下,每次猜一个数字x都可以将当前的答案区间[a, b]缩小成[a, x]或者[x, b],最后当区间的两个端点相同的时候就得到了答案。这就是二分的思想,我们知道在有序的数列中查找一个数最差需要log2n次,因此这道题我们只要求log2(x) < n的最大x,即x < 2n。
Code:
// 为了保险起见我把pow的结果强转成longlong
int main()
{
int T;
cin >> T;
while(T--){
int n;
cin >> n;
cout << (long long)pow(2, n)-1 << endl;
}
return 0;
}
1005:求n个整数中倒数第二小的数。
分析:排序一下输出第二个数就好了,sort默认是按从小到大排序的。
Code:
const int MAXN = 100 + 10;
int arr[MAXN];
int main()
{
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
sort(arr, arr + n);
cout << arr[1] << endl;
}
return 0;
}
1009:求多个数的最小公倍数lcm。
分析:1001的强化版,两个两个合并就可以了。
lcm(a, b, c) = lcm(lcm(a, b), c)。
Code:
int main()
{
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
int ans;
for (int i = 0; i < n; i++)
{
int v;
cin >> v;
if (i == 0) // 先把第一个数字当做前面的lcm
ans = v;
else
{
int d = __gcd(ans, v);
ans = ans*v/d; // 前面合并完的lcm继续合并
}
}
cout << ans << endl;
}
return 0;
}
1012:排序。
分析:sort。
Code:
const int MAXN = 1000 + 10;
int arr[MAXN];
signed main()
{
int T;
cin >> T;
while(T--){
int n;
cin >> n;
for(int i = 0; i < n; i++){
cin >> arr[i];
}
sort(arr, arr+n);
for(int i = 0; i < n; i++){
if(i) cout << " "; // 除了第一个元素前面都加
cout << arr[i];
}
cout << endl;
}
return 0;
}
1016:求lowbit,即求某一个数的二进制表示中最低的一位1。
分析:很套路,结论很简单,巧妙利用位运算,lowbit是树状数组的核心功能。
Code:
int main()
{
while(cin >> x, x){
cout << (x&(-x)) << endl; // x-(x&(x-1))也可以
}
return 0;
}
证明:
-
x - (x & (x - 1)):
首先我们知道 x & (x - 1) 的作用是消除x的二进制串中最低位的1,如图。
而消除掉的部分正好就是我们要求的部分,因此用 x - (x & (x - 1)) 即可。
-
x & (-x): 经典写法
利用了计算机补码的特点,-x的补码就是x的二进制串按位取反,最后+1。比如上面的 x = 10100B,那么-x = 01100B,进行一次与运算&之后100前面的数字就消除掉了,只剩下我们需要的数字。重点理解一下!
1022:完全背包问题。
分析:这道题目一看到就想到完全背包问题,如果没学过的同学可以去学习一下,背包问题是经典的动态规划dp问题。不过数据范围很小,正解可能是贪心。
Code:
const int MAXN = 10000 + 10;
int n, v;
int dp[MAXN];
int volume[10], value[10];
void park(int val, int vol)
{
for (int j = vol; j <= v; j++)
{
dp[j] = max(dp[j], dp[j - vol] + val);
}
}
signed main()
{
int T;
cin >> T;
while(T--){
cin >> v;
n = 3;
volume[1] = value[1] = 150;
volume[2] = value[2] = 200;
volume[3] = value[3] = 350;
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++){
park(value[i], volume[i]);
}
cout << v-dp[v] << endl;
}
return 0;
}
二、思维题
1002:计算从a点b分到s点t分时针和分针重合的次数。
分析:说实话这道题不是很好写,我也是看网上的代码才会的,这里引用一下。(我也不是很理解)
时针旋转1周,即12小时内,时针和分针重合了11次,且连续两次重合相隔的时间相同;
所以可计算:60*12/11 = 720/11(分钟)
即:下次重合的时间是 1 点 60/11 分 ;
最少经过 720/11 分钟,时针和分针就能重合一次。
Code:
int main()
{
int a, b, c, d, x, y, t;
while (cin >> a >> b >> c >> d, a+b+c+d)
{
a %= 12;
c %= 12;
x = (a * 60 + b) * 11;
y = (c * 60 + d) * 11;
if (x > y)
y += 720 * 11;
t = y / 720 - x / 720;
if (x == 0)
t++;
cout << t << endl;
}
return 0;
}
1013:求NN的最后一位数字
分析:暴力方法不可取,数字太大存不下,我们只需要考虑个位数字,看看是否有规律。
对于输入的N,是正整数,因此个位数只能是0~9。
0 -> 0 -> 0 -> 0 -> 0
1 -> 1 -> 1 -> 1 -> 1
2 -> 4 -> 8 -> 6 -> 2
3 -> 9 -> 7 -> 1 -> 3
4 -> 6 -> 4 -> 6 -> 4
5 -> 5 -> 5 -> 5 -> 5
6 -> 6 -> 6 -> 6 -> 6
7 -> 9 -> 3 -> 1 -> 7
8 -> 4 -> 2 -> 6 -> 8
9 -> 1 -> 9 -> 1 -> 9
通过观察我们发现0~9所有数字序列都可以看成以4为周期的循环序列,因此我们只需要求出前四项即可。
Code:
int arr[5];
int main()
{
int T;
cin >> T;
while(T--){
int n;
cin >> n;
arr[1] = n%10;
arr[2] = arr[1]*n%10;
arr[3] = arr[2]*n%10;
arr[0] = arr[3]*n%10;
cout << arr[n%4] << endl;
}
return 0;
}
1018:求不同的人从出生到18岁生日经过的总天数。
分析:首先考虑没有18岁生日的情况,只可能那个人是02.29生的,那么他就不会有18岁生日。接下来考虑闰年的问题,闰年年份要么能被4整除且不能被100整除,要么能被400整除。一个人的出生月份会影响总天数,如果一个人出生在2月之后,那么不管今年是不是闰年都不影响,闰年将会从第二年开始影响;如果一个人出生在2月及之前,那么闰年从这一年就开始影响。
Code:
int checkYear(int year) // 判断year是否为闰年
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
return 1;
else
return 0;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int year, month, day;
scanf("%d-%d-%d", &year, &month, &day); // scanf好控制输入
if (month == 2 && day == 29)
{
printf("-1\n");
}
else
{
int sum = 0;
if (month > 2)
{
for (int y = year + 1; y <= year + 18; y++)
{
if (checkYear(y))
sum += 366;
else
sum += 365;
}
}
else
{
for (int y = year; y <= year + 17; y++)
{
if (checkYear(y))
sum += 366;
else
sum += 365;
}
}
printf("%d\n", sum);
}
}
return 0;
}
1020:不连续吃相同的糖果,问是否能全部吃完。
分析:因为不能连续吃相同的糖果,那么我们应该最先考虑处理掉数量最多的同类糖果,联想高中的插空法,假设有n个相同的糖果,先放好n个糖果,再将剩下的所有排好序的糖果依次插空,如果全部插满n-1个空,那么最大的问题已经解决了,剩下如果有多余的糖果再依次插入新糖果序列的空中,返回Yes,若插不满则返回No。
Code:
int main()
{
int T;
cin >> T;
while(T--){
int n;
cin >> n;
int maxV = 0;
int sum = 0;
for(int i = 0; i < n; i++){
int v;
cin >> v;
maxV = max(maxV, v);
sum += v;
}
if(sum-maxV+1 < maxV){ // 插不满
cout << "No" << endl;
}
else{
cout << "Yes" << endl;
}
}
return 0;
}
三、模拟题
1004:计算圆周率到小数点后5n位。
分析:使用圆周率收敛级数进行模拟计算,大家一起学习一下 = =。
Code:
int a[5000];
int main()
{
float s;
int b, x, n, c, i, j, d, l;
while (cin >> x, x)
{
x *= 5;
for (s = 0, n = 1; n <= 5000; n++) //累加确定项数.
{
s = s + log10((2 * n + 1) / n);
if (s > x + 1)
break;
}
for (i = 0; i <= x + 5; i++)
a[i] = 0;
for (c = 1, j = n; j >= 1; j--) //按公式分布计算。
{
d = 2 * j + 1;
for (i = 0; i <= x + 4; i++) //各位实施除2j+1.
{
a[i] = c / d;
c = (c % d) * 10 + a[i + 1];
}
a[x + 5] = c / d;
for (b = 0, i = x + 5; i >= 0; i--) //各位实施乘j
{
a[i] = a[i] * j + b;
b = a[i] / 10;
a[i] = a[i] % 10;
}
a[0] = a[0] + 1;
c = a[0]; //整数加1.
}
for (b = 0, i = x + 5; i >= 0; i--) //按公式各位乘2
{
a[i] = a[i] * 2 + b;
b = a[i] / 10;
a[i] = a[i] % 10;
}
cout << a[0] << "." << endl; //诸位输出计算结果。
int now = 1;
int index = 1;
while(now <= x/5){
for(int i = 0; i < 10; i++){
if(now <= x/5){
cout << " ";
for(int j = 0; j < 5; j++){
cout << a[index++];
}
now++;
}
else{
break;
}
}
cout << endl;
}
}
return 0;
}
1007:计算e。
分析:只要10项,根据题目给的公式模拟即可。
注意:保留小数问题,后面有一项最后是0,也要输出。
Code:
double ans[MAXN];
int main()
{
int fac; // 保存阶乘
for(int i = 0; i < 10; i++){
if(i == 0) fac = 1;
else fac = fac*i;
if(i == 0) ans[i] = 1;
else ans[i] = ans[i-1]+1.0/fac;
}
cout << "n e" << endl;
cout << "- -----------" << endl;
for(int i = 0; i < 10; i++){
if(i < 3) cout << i << " " << ans[i] << endl;
else cout << fixed << setprecision(9) << i << " " << ans[i] << endl;
}
return 0;
}
1008:统计满足 (a2+b2+m)%(ab) = 0 的 (a, b)对数。
分析:双重循环遍历n,枚举a和b检查是否满足条件,统计结果即可。
注意:(a, b) 和 (b, a) 算一个,每个输出块之间要输出空行。
Code:
int main()
{
int T;
cin >> T;
while(T--){
int kase = 1;
while(cin >> n >> m, n+m){
int ans = 0;
for(int i = 1; i < n; i++){
for(int j = i+1; j < n; j++){ // i+1开始,避免重复
if((i*i+j*j+m)%(i*j) == 0){
ans++;
}
}
}
cout << "Case " << kase++ << ": " << ans << endl;
}
if(T) cout << endl;
}
return 0;
}
1011:计算[i, j]之间所有数能达到的最大的3n+1循环次数。
分析:遍历 [i, j] 之间的所有数,进行模拟计算循环次数,维护最大值。
Code:
int calc(int x){ // 计算x的3n+1循环次数
int cnt = 0;
while(x != 1){
if(x%2) x = x*3+1;
else x = x/2;
cnt++;
}
return cnt+1;
}
int main()
{
int a, b;
while (cin >> a >> b)
{
int aa = min(a, b), bb = max(a, b); // 确保a小于等于b
int ans = 0;
for (int i = aa; i <= bb; i++)
{
ans = max(ans, calc(i));
}
cout << a << " " << b << " " << ans << endl;
}
return 0;
}
1015:模拟加减乘除。
分析:根据输入的运算符进行分类处理即可。
注意:只有当结果是浮点数的时候才保留小数,进行除法的时候结果可能是浮点数也可能是整数!
Code:
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
getchar();
double a,b,sum;
char op;
scanf("%c %lf%lf",&op,&a,&b);
switch(op)
{
case '+': sum=a+b;break;
case '-':sum=a-b;break;
case '*':sum=a*b;break;
case '/':sum=a/b;break;
}
if(sum!=(int)sum) // 结果是浮点数的时候才保留
{
printf("%.2f\n",sum);
}
else
{
printf("%d\n",(int)sum);
}
}
return 0;
}
1017:求出所有满足10、12、16进制下按位相加结果相同的四位数。
分析:短除法模拟进制转化并且求和。
Code:
int calc(int x, int base){ // 模拟短除法
int sum = 0;
while(x){
sum += x%base;
x /= base;
}
return sum;
}
int main()
{
for(int i = 2992; i <= 9999; i++){
int a = calc(i, 10), b = calc(i, 12), c = calc(i, 16);
if(a == b && b == c){
cout << i << endl;
}
}
return 0;
}
1019:计算GPA。
分析:按要求统计相除即可。
注意:学分和成绩可能是实数,用double,并且可能存在学分为0的情况,这也不是有效成绩记录。
Code:
int main()
{
while(cin >> n){
double sum1 = 0, sum2 = 0; // 对精度有要求,用double,不要用float
for(int i = 0; i < n; i++){
double a, b;
cin >> a >> b;
if(b == -1) continue; // 缺考,不统计
sum1 += a;
if(b >= 90) sum2 += 4*a;
else if(b >= 80) sum2 += 3*a;
else if(b >= 70) sum2 += 2*a;
else if(b >= 60) sum2 += 1*a;
else sum2 += 0;
}
if(sum1 == sum2 && sum2 == 0){ // 包含了学分为0以及缺考的情况
cout << -1 << endl;
}
else{
cout << fixed << setprecision(2) << sum2/sum1 << endl;
}
}
return 0;
}
1021:统计得到给定分数x的学生人数。
分析:把学生的成绩先保存下来,再遍历一遍统计结果。
Code:
const int MAXN = 10000 + 10;
int arr[MAXN];
int main()
{
while(scanf("%d", &n) == 1 && n){
for(int i = 0; i < n; i++){
scanf("%d", &arr[i]);
}
int target;
scanf("%d", &target);
int cnt = 0;
for(int i = 0; i < n; i++){
if(arr[i] == target) cnt++;
}
printf("%d\n", cnt);
}
return 0;
}
1023:按照规则翻转数字。
分析:有负号先输出负号,再统计后导0的个数,将其之前的字符串翻转。
Code:
int main()
{
int T;
cin >> T;
while(T--){
string str;
cin >> str;
if(str[0] == '-'){ // 先输出负号
cout << "-";
str = str.substr(1); // 去掉负号
}
int cnt = 0;
for(int i = str.length()-1; i >= 0; i--){ // 统计后导0的个数
if(str[i] != '0') break;
cnt++;
}
reverse(str.begin(), str.end()-cnt); // 调库,翻转后导0之前的字符串
cout << str << endl;
}
return 0;
}
1024:验证角谷猜想。
分析:按照规则模拟即可。
Code:
vector<int> ans; // 使用了C++的vector容器,相当于可以动态伸长的数组
int main()
{
int T;
cin >> T;
while(T--){
ans.clear(); // 清空容器
int n;
cin >> n;
while(n != 1){
if(n%2) ans.push_back(n), n = n*3+1; // 把过程元素记录到容器中
else n = n/2;
}
if(ans.size()){ // 若容器非空
for(int i = 0; i < ans.size(); i++){
if(i) cout << " ";
cout << ans[i];
}
cout << endl;
}
else{
cout << "No number can be output !" << endl;
}
}
return 0;
}
1025:字符串翻转相加,直到变成回文串。
分析:涉及到字符串相加,于是我就直接上了高精,模拟过程记录下来即可。
Code:
const int L = 1100;
string add(string a, string b) // 高精加法,只限两个非负整数相加,只要会用就可以
{
string ans;
int na[L] = {0}, nb[L] = {0};
int la = a.size(), lb = b.size();
for (int i = 0; i < la; i++)
na[la - 1 - i] = a[i] - '0';
for (int i = 0; i < lb; i++)
nb[lb - 1 - i] = b[i] - '0';
int lmax = la > lb ? la : lb;
for (int i = 0; i < lmax; i++)
na[i] += nb[i], na[i + 1] += na[i] / 10, na[i] %= 10;
if (na[lmax])
lmax++;
for (int i = lmax - 1; i >= 0; i--)
ans += na[i] + '0';
return ans;
}
int check(string str){ // 检测是否是回文串
int len = str.length();
for(int i = 0; i < (len+1)/2; i++){
if(str[i] != str[len-i-1]) return 0;
}
return 1;
}
vector<string> ans; // C++的容器,动态伸长的数组
int main()
{
string str;
while(cin >> str){
ans.clear(); // 清空容器
while(!check(str)){ // 如果不是回文串继续变换
ans.push_back(str); // 记录过程字符串
string tmp = str;
reverse(tmp.begin(), tmp.end()); // 调库翻转字符串
str = add(str, tmp); // 高精加法
}
ans.push_back(str); // 记录回文串
cout << ans.size()-1 << endl;
for(int i = 0; i < ans.size(); i++){
if(i) cout << "--->";
cout << ans[i];
}
cout << endl;
}
return 0;
}
1026:模拟计算机。
分析:按照规则模拟即可。
Code:
int main()
{
int m1, m2, r1, r2, r3;
while (cin >> m1 >> m2)
{
r1 = r2 = r3 = 0;
string op;
cin >> op;
for (int i = 0; i < op.length(); i++)
{
switch (op[i])
{
case 'A':
r1 = m1;
break;
case 'B':
r2 = m2;
break;
case 'C':
m1 = r3;
break;
case 'D':
m2 = r3;
break;
case 'E':
r3 = r1 + r2;
break;
case 'F':
r3 = r1 - r2;
break;
}
}
cout << m1 << "," << m2 << endl;
}
return 0;
}
四、数论题
1006:给定a和b,求是否存在x、y满足 x + y = b x+y = b x+y=b, x ⋅ y = b x\cdot y = b x⋅y=b。
分析: x ⋅ y = b x\cdot y = b x⋅y=b,则 x = b y x = \frac{b}{y} x=yb,代入 x + y = b x+y = b x+y=b,得 b y + y = a \frac{b}{y}+y = a yb+y=a,即 y = a ± a 2 − 4 ∗ b 2 y = \frac{a\pm\sqrt{a^{2}-4\ast b}}{2} y=2a±a2−4∗b。
故我们只需要把a和b代入公式,再把求出来的x和y代入验证就可以了。
Code:
int main()
{
while(cin >> n >> m, n+m){
int a = n*n-4*m;
if(a < 0){ // 若a^2-4b < 0则无解
cout << "No" << endl;
}
else{
int x1 = (n+sqrt(a))/2;
int y1 = n-x1;
int x2 = (n-sqrt(a))/2;
int y2 = n-x2;
if(x1*y1 == m || x2*y2 == m){ // 将可能的(x, y)代入验证
cout << "Yes" << endl;
}
else{
cout << "No" << endl;
}
}
}
return 0;
}
1010:统计一个整数的组合方式。
分析:不多说了,母函数裸题,要学的同学自行百度了~~毕竟我不是数论手,不是很会(太菜了…)。
Code:
const int MAXN = 1e4 + 10;
int n;
int c1[MAXN];
int c2[MAXN];
int main()
{
while (cin >> n)
{
for (int i = 0; i <= n; ++i)
{
c1[i] = 1;
c2[i] = 0;
}
for (int i = 2; i <= n; ++i)
{
for (int j = 0; j <= n; ++j)
{
for (int k = 0; k + j <= n; k += i)
{
c2[j + k] += c1[j];
}
}
for (int j = 0; j <= n; ++j)
{
c1[j] = c2[j];
c2[j] = 0;
}
}
cout << c1[n] << endl;
}
return 0;
}
1014:已知f(x) = 5x13+13x5+kax,给定k,求最小的自然数a满足对任意x有f(x)%65=0。
分析:既然要求对任意x都满足,不妨设x = 1,则f(x) = 18+ka,问题转化为 (18+ka)%65 = 0,即 ka%65 = 47。
因此只需要枚举a的值,从1到65,检查是否满足要求,是则输出答案,否则无解,因此%65的缘故,a再大就出现循环。
Code:
int main()
{
while(cin >> n){
int ans = -1;
for(int i = 0; i < 65; i++){
if(n*i%65 == 47){
ans = i;
break;
}
}
if(ans == -1){
cout << "no" << endl;
}
else{
cout << ans << endl;
}
}
return 0;
}
【END】感谢观看!