目录
初赛心得
2022/4/9,本人于大一下初次参加面向各大高校的编程竞赛,在参赛选拔时,对算法的了解仅为皮毛,学习acwing算法基础课,仅能掌握快速幂、前缀和等初级算法。经过大约一个月的学习,初步了解二分、前缀和、位运算、STL库(观看数组实现视频,无法独立完成)、暴搜、简单并查集、Kruskal(克鲁斯卡尔)求最小生成树、Dijkstra(迪杰斯特拉 )求最短路、快速幂、简单博弈论(Nim游戏),知道DP(仅观看推导视频,跟抄代码,从未自己实现),会做简单贪心,并能够做出简单推导,学习了微扰法证明国王游戏(可在算法竞赛进阶指南中查阅)。
2022/4/28,蓝桥杯成绩喜人,进入国赛,目前为止,已经掌握初级算法。能够完成二分、前缀和、位运算、双指针、STL库、暴搜、并查集、最小生成树、最短路、简单博弈论、贪心推导。还无法实现DP状态转移方程的独立推导。
A: 九进制转十进制
A.1 问题描述
九进制正整数 (2022) 9 转换成十进制等于多少?
A.2 比赛回顾
简单进制转换(开始的时候还差点算错,后来看到X进制一题,觉得不太对劲,回来才更正了)
B: 顺子日期
B.1 问题描述
小明特别喜欢顺子。顺子指的就是连续的三个数字: 123 、 456 等。顺子日 期指的就是在日期的 yyyymmdd 表示法中,存在任意连续的三位数是一个顺 子的日期。例如 20220123 就是一个顺子日期,因为它出现了一个顺子: 123 ; 而 20221023 则不是一个顺子日期,它一个顺子也没有。小明想知道在整个 2022 年份中,一共有多少个顺子日期
B.2 比赛回顾
目前来说题目还有争议(做的时候没想那么多,直接在纸上推)
C: 刷题统计
C.1 问题描述
【问题描述】小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题?【输入格式】输入一行包含三个整数 a , b 和 n .【输出格式】输出一个整数代表天数。【样例输入】10 20 99【样例输出】8【评测用例规模与约定】对于 50 % 的评测用例, 1 ≤ a , b , n ≤ 10 6 .对于 100 % 的评测用例, 1 ≤ a , b , n ≤ 10 18 .
C.2 比赛回顾
做的时候没想那么多,也不太看数据,最多知道a b要用long long,对于时间复杂度的关注是我在比赛后的这两三天只内,听学长讲,xyc讲才开始的。
C.3 题解代码
#include<iostream>
using namespace std;
//对于100%的样例,a,b,n为long long
#define int long long
int n;
int a,b;
int ans;
signed main()
{
cin>>a>>b>>n;
int t=n/(a*5+b*2);//算出每周做题,总题数/周,下取整
int T=n%(a*5+b*2);//计算剩余题目
int cnt=0;//统计天数
for(int i=0;i<7;i++)
{//i不会超过7,即如果超过7天,则会被算入t
if(T<=0)break;//如果T减到零以后结束循环,可能半天做完,也得算一天
if(i==5&&i==6)T-=b;
else T-=a;
cnt=i+1;
}
cout<<t*7+cnt;//t是以周为单位的
return 0;
}
c题,对于全部数据n要超1e8的,如果按天计算,必超时。
D: 修剪灌木
D.1 问题描述
【问题描述】爱丽丝要完成一项修剪灌木的工作。有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。灌木每天从早上到傍晚会长高 1 厘米,而其余时间不会长高。在第一天的早晨,所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。【输入格式】一个正整数 N ,含义如题面所述。【输出格式】输出 N 行,每行一个整数,第行表示从左到右第 i 棵树最高能长到多高。【样例输入】3【样例输出】424【评测用例规模与约定】对于 30 % 的数据, N ≤ 10 .对于 100 % 的数据, 1 < N ≤ 10000 .
D.2 比赛回顾
当时做的时候脑子已经想不了太多了,直接暴力模拟,来回砍三次即可,我还算了一下时间复杂度,不是嵌套循环,应该是3*n,结果不会超时。
D.3 题解代码
#include<iostream>
using namespace std;
int n;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cout<<max(i*2,(n-i-1)*2)<<endl;
}
return 0;
}
经推导可得,第k棵树(k为1~n)的最大高度为,k-1 或 n-k 的最大值*2
E: X 进制减法
E.1 问题描述
【问题描述】进制规定了数字在数位上逢几进一。X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则X 进制数 321 转换为十进制数为 65 。现在有两个 X 进制表示的整数 A 和 B ,但是其具体每一数位的进制还不确定,只知道 A 和 B 是同一进制规则,且每一数位最高为 N 进制,最低为二进制。请你算出 A − B 的结果最小可能是多少。请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制。【输入格式】第一行一个正整数 N ,含义如题面所述。第二行一个正整数 M a ,表示 X 进制数 A 的位数。第三行 M a 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示。第四行一个正整数 M b ,表示 X 进制数 B 的位数。第五行 M b 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示。请注意,输入中的所有数字都是十进制的。【输出格式】输出一行一个整数,表示 X 进制数 A − B 的结果的最小可能值转换为十进制后再模 1000000007 的结果。【样例输入】11310 4 031 2 0【样例输出】94【样例说明】当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法得到的差最小。此时 A 在十进制下是 108 , B 在十进制下是 14 ,差值是 94 。【评测用例规模与约定】对于 30 % 的数据, N ≤ 10; M a , M b ≤ 8 .对于 100 % 的数据, 2 ≤ N ≤ 1000; 1 ≤ M a , M b ≤ 100000; A ≥ B
E.2 比赛回顾
做到这个地方,理解题意已经开始困难了,开始没搞懂他给的样例是怎么转换的,就先看了后面的二维前缀和,读懂题目之后,其实当时的我并不知道这会是一个贪心题目,只是能够感觉到,X应该是a[i]+1,b[i]+1中的最大值,想到这里就很明晰了,先算出X,再用该位进制*(a[i]-b[i])即可
E.3 题解代码
#include<iostream>
using namespace std;
const int mod=1000000007;
const int N=1e5+10;
int n,A,B;
int a[N],b[N],x[N];
long long ans;
signed main()
{
cin>>n;//n用不到
cin>>A;//A的位数
for(int i=A;i>0;i--)cin>>a[i];
cin>>B;//B的位数
for(int i=B;i>0;i--)cin>>b[i];
//比较算出x,注意x最小为2
for(int i=1;i<=A;i++)
{
x[i]=max(a[i]+1,b[i]+1);
if(x[i]<2)x[i]=2;
}
long long int k=1;
for(int i=1;i<=A;i++)
{
ans+=((a[i]-b[i])*k)%mod;
ans=(ans+mod)%mod;//可能出现负数
k=(k*x[i])%mod;//对进制累乘
}
cout<<ans<<endl;
return 0;
}
(ans+mod)%mod是一个很神奇的运算,可以消除负数取模运算规律的影响。
F: 统计子矩阵
F.1 问题描述
【问题描述】给定一个 N × M 的矩阵 A ,请你统计有多少个子矩阵 ( 最小 1 × 1 ,最大N × M ) 满足子矩阵中所有数的和不超过给定的整数 K ?【输入格式】第一行包含三个整数 N , M 和 K .之后 N 行每行包含 M 个整数,代表矩阵 A .【输出格式】一个整数代表答案。【样例输入】3 4 101 2 3 45 6 7 89 10 11 12【样例输出】19【样例说明】满足条件的子矩阵一共有 19 ,包含:大小为 1 × 1 的有 10 个。大小为 1 × 2 的有 3 个。大小为 1 × 3 的有 2 个。大小为 1 × 4 的有 1 个。大小为 2 × 1 的有 3 个。【评测用例规模与约定】对于 30 % 的数据, N , M ≤ 20 .对于 70 % 的数据, N , M ≤ 100 .对于 100 % 的数据, 1 ≤ N , M ≤ 500; 0 ≤ A i j ≤ 1000; 1 ≤ K ≤ 250000000 .
F.2 比赛回顾
做这个题的时候,只能知道要使用二维前缀和,比赛时还并不会使用双指针,虽然知道会超,但是也比没做强。最终提交的代码为4个for的二维前缀和,在调试前缀和时,一个小bug,其实前缀和的函数已经写正确了,只是我在调用输出时忘记加上函数名了,改了半天。考试真的会紧张,大脑只能做出一些直观的感觉性判断。在备赛时,熟悉掌握各种算法的模板、代码流程、特性真的很重要。考场回忆思想,摸索代码会浪费大量时间,还特别容易出错。
F.3 题解代码
#include<iostream>
#define int long long
using namespace std;
const int N=510;
int n,m,k;
int a[N][N];
int res;
signed main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%lld",&a[i][j]);
a[i][j]+=a[i-1][j];
}
for(int i=1;i<=n;i++)//矩阵上端点
for(int j=i;j<=n;j++)//矩阵下端点
for(int l=1,r=1,sum=0;r<=m;r++)
{
sum+=a[j][r]-a[i-1][r];
while(sum>k)
{
sum-=a[j][l]-a[i-1][l];
l++;
}
res+=r-l+1;
}
cout<<res;
return 0;
}
本题解法需要用到前缀和,计算时,如果使用4个for循环的二维前缀和,无法通过全部样例。正确的做法是,采用一维前缀和的形式+双指针算法。我们每次确定矩阵的上端点和下端的,通过双指针来扫描计算每一列的总和。