目录
一.行列找规律问题
1.Excel的列
在 Excel 中,列的名称使用英文字母的组合。前 26 列用一个字母,依次为 A 到 Z,接下来 26*26 列使用两个字母的组合,依次为 AA 到 ZZ,依次循环。
请问第 2022 列的名称是什么?
#include <iostream>
using namespace std;
int main()
{
int i,j;
int n = 2022%(26+26*26);
for(i=0;i<26;i++)
for(j=0;j<26;j++)
if((i+1)*26+j+1 == n-26) printf("%c%c",65+i%26, 65+j%26);
return 0;
}
技巧:对于存在循环的情况,要求第m个数是什么,先算出一个循环内的个数n,再进行m%n运算,然后用这个结果再去遍历即可。
变式
在 Excel 中,列的名称使用英文字母的组合。前 26 列用一个字母,依次为 A 到 Z,接下来 26*26 列使用两个字母的组合,依次为 AA 到 ZZ,然后是AAA到ZZZ,依此类推。
请问第 2022 列的名称是什么?
(其实就相当与转化为26进制了,只是都用字母来表示了)
#include <iostream>
using namespace std;
int main()
{
int n = 2022;
int arr[10]={0};
int i = 0;
while(n>0){
arr[i++] = n%26;
n /= 26;
}
for(int j = i-1;j>=0;j--) printf("%c",arr[j]+65-1);
return 0;
}
这里arr【 j 】+65还要减去1,是因为这里下标是从1到26 ,而不是从0到25
2.最小子矩阵
小蓝有一个 100 行 100 列的矩阵,矩阵的左上角为 1。其它每个位置正好比其左边的数大 2,比其
上边的数大 1 。例如,第 1 行第 2 列为 3,第 2 行第 2 列 为 4,第 10 行第 20 列为 48。
小蓝想在矩阵中找到一个由连续的若干行、连续的若干列组成的子矩阵,使得其和为 2022,
请问这个子矩阵中至少包含多少个元素(即子矩阵的行数和列数的乘积)
#include <iostream>
using namespace std;
int main()
{
int s[100][100] = {1};
for(int j = 0; j<100; j++)
s[0][j] = 2*j+1;
for(int i = 1; i<100; i++)
for(int j = 0; j<100; j++)
s[i][j] = s[i-1][j]+1;
int sum = 0;
int temp = 10000;
for(int i1 = 0; i1<100; i1++)
for(int i2 = i1; i2<100; i2++)
for(int j1 = 0; j1<100; j1++)
for(int j2 = j1; j2<100; j2++)
{
for(int i = i1; i<=i2; i++)
for(int j = j1; j<=j2; j++)
{
sum += s[i][j];
if(sum==2022)
{
temp = min(temp,(i2-i1+1)*(j2-j1+1));
goto Hello;
}
if(sum>2022)
goto Hello;
}
Hello:
sum = 0;
}
cout<<temp;
return 0;
}
//Wrong Answer: 10
//Correct Answer: 12
正解:
#include<iostream>
#include <vector>
using namespace std;
typedef long long ll;
vector<vector<int> > adj(100);
int inline sum(int a, int b, int c, int d)
{
int res = 0;
for (int i = a; i < a + c; i++)
{
for (int j = b; j < b + d; j++)
{
res += adj[i][j];
}
}
return res;
}
int main()
{
for (int i = 0; i < 100; i++)
{
adj[i].push_back(i + 1);
for (int j = 1; j < 100; j++)
{
adj[i].push_back(adj[i][j - 1] + 2);
}
}
int ans = 10001;
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
for (int k = 1; k <= 100 - i; k++)
for (int l = 1; l <= 100 - j; l++)
if (sum(i, j, k, l) == 2022)
{
ans = min(ans, k * l);
break;
}
else if (sum(i, j, k, l) > 2022)
break;
}
}
}
}
cout << ans << endl;
return 0;
}
二.日期问题
1. 相等日期
问题描述
对于一个日期,我们可以计算出年份的各个数位上的数字之和,也可以分别计算月和日的各位数字之和。请问从 1900 年 1 月 1 日至 9999 年 12 月 31 日,总共有多少天,年份的数位数字之和等于月的数位数字之和加日的数位数字之和。
例如,2022年11月13日满足要求,因为 2+0+2+2=(1+1)+(1+3) 。
请提交满足条件的日期的总数量。
#include <iostream>
using namespace std;
int mon[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
int Judge(int y){
return (y%4==0 && y%100!=0) || y%400==0;
}
int fun(int n){
int sum = 0;
while(n>0){
sum += n%10;
n /= 10;
}
return sum;
}
int main()
{
int y,m,d;
int cnt= 0;
for(y = 1900;y<10000;y++){
if(Judge(y)) mon[1] = 29;
else mon[1] = 28;
for(m = 1;m<13;m++){
for(d = 1;d<mon[m-1]+1;d++){
if(fun(y) == fun(m)+fun(d)) cnt++;
}
}
}
cout<<cnt;
return 0;
}
//Answer: 70910
坑:if(Judge(y)) mon[1] = 29;
else mon[1] = 28;
不能写成 mon[1] += Judge(y) ;因为这样就实现不了重置mon[1] 为28了
2.晨跑小蓝
小蓝每周六、周日都晨跑,每月的 1、11、21、31日也晨跑。其它时间不晨跑。
已知 2022年1月1日是周六,请问小蓝整个2022年晨跑多少天?
// 像这种会有重复的可以定义一个365天的数组,这样反复赋值也不会影响(有点像flood fill)
#include <iostream>
using namespace std;
int month[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
int main()
{
int cnt = 0;
int arr[366] = {0};
int i;
for(i = 0; i*7+2<=365; i++)
{
arr[i*7+1] = 1;
arr[i*7+2] = 1;
}
if(i*7+1<=365) arr[i*7+1] = 1;
int d1,d2,d3,d4;
d1 = 1,d2 = 11,d3 = 21,d4 = 31;
for(int i = 0; i<12; i++)
{
arr[d1] = 1;
arr[d2] = 1;
arr[d3] = 1;
if(month[i]==31)
arr[d4] = 1;
d1+=month[i];
d2+=month[i];
d3+=month[i];
d4+=month[i];
}
for(int i = 1; i<=365; i++)
if(arr[i]) cnt++;
cout<<cnt;
return 0;
}
// Answer: 138
优化:
#include<iostream>
using namespace std;
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int main()
{
int ans = 0;
int week = 6;
for (int month = 1; month <= 12; month ++)
{
for (int day = 1; day <= days[month]; day ++)
{
ans += (week == 6 || week == 0 || day % 10 == 1);
week = (week + 1) % 7;
}
}
cout << ans << endl;
return 0;
}
三.暴力枚举法
1.小蓝的数
小蓝有 30 个数,分别为:99, 22, 51, 63, 72, 61, 20, 88, 40, 21, 63, 30, 11, 18, 99, 12, 93, 16, 7, 53, 64, 9, 28, 84, 34, 96, 52, 82, 51, 77 。
小蓝可以在这些数中取出两个序号不同的数,共有 30*29/2=435 种取法。
请问这 435 种取法中,有多少种取法取出的两个数的乘积大于等于 2022 。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
错解:
#include <iostream>
using namespace std;
int main()
{
int cnt = 0;
int arr[30];
for(int i = 0;i<30;i++){
scanf("%d",&arr[i]);
getchar();
}
for(int i = 0;i<30;i++)
for(int j = 0;j<30;j++)
if(arr[i]*arr[j]>=2022) cnt++;
cout<<cnt;
return 0;
}
错误原因:
1.435种取法,而用的嵌套循环多弄了一倍(就相当于对称矩阵,只要求计算下三角即可)
修正:内循环约束条件改为 j <= i 即可
2.序号不同的数!!!而这里完全没注意到这个限制了
修正:改为 j < i (也就是下三角出去对角线了)
经验:
对于99, 22, 51, 63, 72, 61, 20, 88, 40, 21, 63, 30, 11, 18, 99, 12, 93, 16, 7, 53, 64, 9, 28, 84, 34, 96, 52, 82, 51, 77 这种不是由空格隔开的数据输入
可以用 scanf("%d",&arr[i]); 加一个 getchar(); 来实现捏!!!
正解:
#include <iostream>
using namespace std;
int main()
{
int cnt = 0;
int arr[30];
for(int i = 0;i<30;i++){
scanf("%d",&arr[i]);
getchar();
}
for(int i = 0;i<30;i++)
for(int j = 0;j<i;j++)
if(arr[i]*arr[j]>=2022) cnt++;
cout<<cnt;
return 0;
}
//Answer: 189
2.区间最小值
小蓝有一个序列 a[1], a[2], …, a[n]。
给定一个正整数 k,请问对于每一个 1 到 n 之间的序号 i,a[i-k], a[i-k+1], …, a[i+k] 这 2k+1 个数中的最小值是多少?当某个下标超过 1 到 n 的范围时,数不存在,求最小值时只取存在的那些值。
输入格式
输入的第一行包含一整数 n。
第二行包含 n 个整数,分别表示 a[1], a[2], …, a[n]。
第三行包含一个整数 k 。
输出格式
输出一行,包含 n 个整数,分别表示对于每个序号求得的最小值。
样例输入
5
5 2 7 4 3
1
样例输出
2 2 2 3 3
#include <iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int* arr = new int[n+1]();
for(int i=1; i<=n; i++)
cin>>arr[i];
int k;
cin>>k;
for(int i=1; i<=n; i++)
{
int left = i-k;
if(left<1) left = 1;
int right = i+k;
if(right>n) right = n;
int minimum = left;
for(int j = left+1; j<=right; j++)
{
if(arr[minimum]>arr[j]) minimum = j;
}
cout<<arr[minimum]<<" ";
}
return 0;
}
可能会超时, 可以使用:单调队列、线段树、动态规划、记忆搜索等等
3.箬治的调和级数
小蓝特别喜欢调和级数 S(n)=1/1+1/2+1/3+1/4+...+1/n 。
请问,n 至少为多大时,S(n)>12 ?
#include <iostream>
using namespace std;
int main()
{
double sum = 0;
double n = 1;
while(sum<12)
{
sum += 1/n++;
}
cout<<n;
return 0;
}
// Wrong Answer: 91381
// Correct Answer: 91380
这里怎么及时发现错误呢:直接把 12 改成 1 和 1.5 看看输出
改为1时输出3,但其实应该输出2;改为1.5时输出4,但应该输出3
故答案每次都多了1,就出问题在n++这里,会多加一次(改成 int n=0,++n 就可以了)
#include <iostream>
using namespace std;
int main()
{
double sum = 0;
double n = 0;
while(sum<=12)
{
sum += 1/++n;
}
cout<<n;
return 0;
}
// Wrong Answer: 91381
// Correct Answer: 91380
4.爱跑步的小明
小明要做一个跑步训练。 初始时,小明充满体力,体力值计为 10000。如果小明跑步,每分钟损耗600的体力。如果小明休息,每分钟增加 300 的体力。体力的损耗和增加都是均匀变化的。
小明打算跑一分钟、休息一分钟、再跑一分钟、再休息一分钟……如此循环。如果某个时刻小明的体力到达0,他就停止锻炼。 请问小明在多久后停止锻炼。
为了使答案为整数,请以秒为单位输出答案。 答案中只填写数,不填写单位。
#include <iostream>
using namespace std;
int main()
{
int time = 0;
int m = 10000;
while(m>600)
{
m -= 600;
time += 60;
m += 300;
time += 60;
}
time += m/10;
cout<<time;
return 0;
}
//Answer: 3880
5.分配口罩
【解题】
这个题就是把数组分成两个部分,求两部分和最小值的包装版本
可以用暴力dfs 也可以dp(0-1背包作背包容量为sum/2的dp),结果填空且只有15个数,直接暴力搜即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
ll num[15]={9090400,8499400,5926800,8547000,4958200,4422600,5751200,4175600,6309600,5865200,6604400,4635000,10663400,8087200,4554000};
ll minn=0x3f3f3f3f;
ll sum;
void dfs(int pos,ll now)
{
if(pos>=15)
{
minn=min(minn,abs(sum-2*now));
return ;
}
dfs(pos+1,now+num[pos]);//选
dfs(pos+1,now); //不选
}
int main()
{
int i;
sum=0;
for(i=0;i<15;i++)
{
sum+=num[i];
}
dfs(0,0);
cout<<minn;
return 0;
}
//ans==2400;