试题 A: 九进制转十进制
手算:1478
试题 B: 顺子日期
有歧义,但是0
肯定不能算在顺子中,因为题目说了“例如 20220123 就是一个顺子日期,因为它出现了一个顺子:123”,如果我没理解错的话,题目的意思是20220123中只有一个顺子,它是123;当然也可以理解为20220123有一个顺子是123。
另一点歧义在于逆序算不算顺子,反正答案不一。
最终我把14改成了4,认为只有123这样的算顺子。
试题 C: 刷题统计
看到数据范围就知道不能模拟了,那就按周算,先看看需要多少周,剩下的题再模拟。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main()
{
LL a, b, n, sum, ans;
cin >> a >> b >> n;
LL oneweek = a * 5LL + b * 2LL;
ans = n / oneweek;
sum = ans * oneweek;
ans *= 7LL;
int day = 1;
while (sum < n) {
ans ++;
if (day == 0 || day == 6) sum += b;
else sum += a;
day = (day + 1) % 7;
}
cout << ans << endl;
return 0;
}
试题 D: 修剪灌木
注意理解直接把树剪为0,我们单独考虑一棵树,什么时候它能长得最高。无非就是从刚把该树剪到0开始算,再次回到该树并剪成0之前的高度最高。也就是保证从该树出发再回到该树的时间最长就能保证树长到最高了。回到该树有两个方向,一个是左边回来,一个是右边回来,记录一个最大值即可,不要忘记到头再回来,是从该树到头的两倍。
还有一点,当输入为1时,需要特别地输出1,因为程序跑得为0,应该不算做测试点。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if (n == 1) return cout << 1 << endl, 0;
for (int i = 1;i <= n;i ++)
cout << max (n - i, i - 1) * 2 << endl;
return 0;
}
试题 E: X 进制减法
很多同学好像没看懂这道题的意思,我也好长时间没看明白,我就使劲凑,看看这道题是怎么把321凑成65的,最后算是想明白了。
如何理解理解最低位的1?其实就是1,如果给的是0,那就是0,也就是说与最低位的进位是没关系的,对十进制数的贡献为1;
如何理解第二位的2?由于最低位是二进制位,即满2进1,进位会加到第二位上,因此第二位是2表示存在两次进位,即最低位存在两次满2,所以该位对于十进制数的贡献为2×2;
如何理解第三位的3?由于第二位是十进制位,即满10进1,进位会加到第三位上,因此第三位是3表示存在三次进位,即第二位存在三次满10,而上面我们讲过,第二位表示满2的次数,所以第三位的3对十进制数的贡献为3×10×2;
那么三个位的贡献之和就是65。
找到规律了吧,其实计算方式就是当前位的数乘以其低位所对应的进制之积,最后累加。
转换会了,题目要求差最小,且输入满足A≥B,我们贪心地思考,如果每一位的进制都尽可能小,那么刚刚计算十进制数时计算的前面若干位进制的乘积就会小,在当前位两数之差不变的情况下,肯定乘积小了A和B的差会更小,所以我们只要保证每一位的进制位大于等于2且尽可能小就行了(思路很简单,所以不证明了,而且我应该也不会证明)
最后不要忘记先加上MOD再取模,防止出现负数。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
const LL MOD = 1000000007;
int n, ma, mb;
LL a[N], b[N], jz[N], base, A, B;
int main()
{
cin >> n;
cin >> ma;
for (int i = ma;i >= 1;i --) cin >> a[i], jz[i] = max (a[i]+1, jz[i]);
cin >> mb;
for (int i = mb;i >= 1;i --) cin >> b[i], jz[i] = max (b[i]+1, jz[i]);
base = 1LL;
for (int i = 1;i <= ma;i ++) {
LL j = max (jz[i], 2LL);
A = (A + a[i] * base % MOD) % MOD;
base = (base * j) % MOD;
}
base = 1LL;
for (int i = 1;i <= mb;i ++) {
LL j = max (jz[i], 2LL);
B = (B + b[i] * base % MOD) % MOD;
base = (base * j) % MOD;
}
cout << (A - B + MOD) % MOD << endl;
return 0;
}
试题 F: 统计子矩阵
只会暴力到不能再暴力的二维前缀和,不知道能骗多少分,有大佬说都是正数,所以递增可以二分试试,但是我能想到的二分思路还是会超时,所以静候神犇的代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 550;
int n, m;
LL k, ans = 0;
LL sum[N][N], a[N][N];
int main()
{
cin >> n >> m >> k;
for (int i = 1;i <= n;i ++)
for (int j = 1;j <= m;j ++) {
cin >> a[i][j];
sum[i][j] = a[i][j] + sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1];
}
for (int i = 1;i <= n;i ++)
for (int j = 1;j <= m;j ++)
for (int a = 1;a <= i;a ++)
for (int b = 1;b <= j;b ++)
if (sum[i][j] - sum[a-1][j] - sum[i][b-1] + sum[a-1][b-1] <= k)
ans ++;
cout << ans << endl;
return 0;
}
试题 G: 积木画
大佬的做法是动态规划,我实在没看懂,过一阵学习一下再补。
试题 H: 扫雷
我在骗分, O ( n 2 ) O(n^2) O(n2)复杂度了, O ( n 2 ) O(n^2) O(n2)应该大家都会。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+10;
struct node {
int x, y, r;
};
int n, m;
int x, y, r;
int ans, st[N];
vector <node> a;
vector <int> v[N], u;
double dist (node a, node b) {
return sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int dfs (int x) {
if (st[x]) return 0;
st[x] = 1;
int res = 1;
for (int i = 0;i < v[x].size();i ++)
if (!st[v[x][i]])
res += dfs (v[x][i]);
return res;
}
int main()
{
cin >> n >> m;
for (int i = 0;i < n;i ++) {
cin >> x >> y >> r;
a.push_back ({x,y,r});
}
for (int i = 0;i < a.size();i ++)
for (int j = 0;j < a.size();j ++)
if (i != j && dist (a[i], a[j]) <= 1.0 * a[i].r)
v[i].push_back (j);
for (int i = 0;i < m;i ++) {
cin >> x >> y >> r;
for (int j = 0;j < n;j ++) {
if (dist ({x, y, r}, a[j]) <= 1.0 * r)
u.push_back (j);
}
}
for (int i = 0;i < u.size();i ++)
ans += dfs (u[i]);
cout << ans << endl;
return 0;
}
试题 I: 李白打酒加强版
动态规划全分。
f[i][j][k]
表示到店i
次,遇花j
次,还有k
斗酒的方案数。
首先必须要能想到虽然酒量可以到
2
100
2^{100}
2100,但是能满足“最后一次遇到花且酒正好喝完”的要求的情况对应的酒量最多也就是100,即m,只有遇花能让酒减少吧。所以k
的上限也就是100了。
转移方程:f[i][j][k] += f[i-1][j][k/2]
表示可由到店转移而来,此时要求k
必须为偶数且i>=1
;f[i][j][k] += f[i][j-1][k+1]
表示可由到花转移而来,此时只要保证j>=1
即可。
初始化:f[0][0][2]=1
,就是最开始到店0次,遇花0次,酒量为2斗。
最后输出f[n][m-1][1]
表示最后一次遇花且酒正好喝完的方案数。
注意取模。
动态规划:
#include<bits/stdc++.h>
using namespace std;
const int N = 110, MOD = 1000000007;
int n, m, f[N][N][N];
int main()
{
cin >> n >> m;
f[0][0][2] = 1;
for (int i = 0;i <= n;i ++)
for (int j = 0;j <= m;j ++)
for (int k = 0;k <= m;k ++) {
if (!(k & 1) && i) f[i][j][k] = (f[i][j][k] + f[i-1][j][k/2]) % MOD;
if (j) f[i][j][k] = (f[i][j][k] + f[i][j-1][k+1]) % MOD;
}
cout << f[n][m-1][1] << endl;
return 0;
}
深搜:(使劲剪枝)
#include<bits/stdc++.h>
using namespace std;
int ans = 0;
int cnt = 0;
int path[100];
void dfs (int n, int m, int ac) {
if (ac <= 0) return ;
if (int(log2(m/ac)) > n) return ;
if (ac > m || (ac >= m && n)) return ;
if (n == 0 && m == 1) {
if (ac == 1) ans ++;
return ;
}
if (n > 0) dfs (n-1, m, ac << 1);
if (m > 0) dfs (n, m-1, ac - 1);
}
int main()
{
int n, m;
cin >> n >> m;
dfs (n, m, 2);
cout << ans << endl;
return 0;
}
试题 J: 砍竹子
确认过眼神是我不会的题。
个人总结
时间飞逝,上一次参加蓝桥杯还是在大二上吧,没想到水平没变,甚至有所退步。
简单记录下心路历程:
-
考场非常吵。都他妈开始麦;
-
监考非常松。开始看要求这科技那科技防作弊,还要求第二个摄像头在身体后方130°处左右……,最后他妈就放手边都没事,放作弊科技就是用来在你切屏看题目的时候多出几个黄牌吓吓你,搞心态用的;
-
厕所战神非常多。平时腰挺好的人也都开始尿频了啊;
-
状态非常差。总是走神,可能是周围写代码的人离得比较远,所以感觉没啥压力,国赛可千万不能松懈;
-
题目非常难。我服了,就怕动规,直接来仨动规(盲猜矩阵那个也得稍微递推一下),而且听说从F开始就已经是LeetCode中等水平了;而且填空就俩,沃日(其实是好事,大题总比一锤子买卖要香);
记录一下做每个题的心路历程:
-
第一题。很好最开始写代码算的,结果算错了,亏了我总结第一次蓝桥杯的经验,明白了蓝桥杯最重要的是把该得的分得到,所以这次预留了一小时检查,果然奇效;
-
第二题。居然存在歧义,离谱,也是检查的时候改的,开始考虑0了,后来只考虑123了;
-
第三题。先模拟的,后发现数据量不对劲,再改的,没出现什么大问题;
-
第四题。稍微想了一下,没有特别特别顺利,不过很快也想出来了,一行代码解决;后面检查的时候把输入为1的情况加上了;
-
第五题。真难理解题目,理解了之后就很简单了;
-
第六题。开寄!以为会是“最大子阵”的类似题目,看来我想多了;
-
第七题。感觉大二的时候应该学过这种题的做法,奈何不争气好久没碰算法了。连搜索都没搜,直接摆烂;
-
第八题。只会 n 2 n^2 n2了,而且第一遍写出来自己写了几个样例后发现健壮性不大行,又改了改差不多能骗点分;
-
第九题。就差把“动态规划”写在题目里了,但是我是fw啊,本来打算先写完搜索骗点分,剩下时间好好思考一下动规,结果写搜索写上瘾了,居然觉得剪枝可以剪出来,时间飞逝,由于腾出来时间检查,所以最后也没时间细想了。感觉自己要是再沉住气,再理性一点绝对能想出来,并不是很难;
-
第十题。当时感觉像线段树,但是根本不会写线段树了都,所以摆!