第一次参加蓝桥杯省赛,补题写博客留个纪念~
问 256 M B 256MB 256MB 的空间可以存储多少个 32 32 32 位二进制整数。
>>> 256*1024*1024*8//32
67108864
小蓝手里有 0 0 0 到 9 9 9 的卡片各 2021 2021 2021 张,共 20210 20210 20210 张,问小蓝可以从 1 1 1 拼到多少。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 B: 卡片
* 解决方案: 模拟。记录各卡片剩余数量,每拼一个数字就减少相应的卡片,直到卡片不足。
*/
#include
using namespace std;
int res[10];
// 拼数字x,失败返回false
bool handle(int x)
{
while (x) {
if (--res[x%10] < 0) return false;
x /= 10;
}
return true;
}
int main()
{
for (int i = 0; i < 10; ++i) res[i] = 2021;
int n = 1; // 从1开始拼
while (handle(n)) { // 拼不出n则退出循环
++n;
}
cout << n-1; // 可以从1拼到n-1
// 输出3181
return 0;
}
给定平面上 20 × 21 20 × 21 20×21 个整点 ( x , y ) ∣ 0 ≤ x < 20 , 0 ≤ y < 21 , x ∈ Z , y ∈ Z {(x, y)|0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z} (x,y)∣0≤x<20,0≤y<21,x∈Z,y∈Z ,问这些点一共确定了多少条不同的直线。
解决方案
掌握直线的表示与去重即可。
用两点式表示直线有
y − y 1 x − x 1 = y 2 − y 1 x 2 − x 1 \frac{y-y_1}{x-x_1}=\frac{y_2-y_1}{x_2-x_1} x−x1y−y1=x2−x1y2−y1
整理得
y = y 2 − y 1 x 2 − x 1 x + x 2 y 1 − x 1 y 2 x 2 − x 1 y=\frac{y_2-y_1}{x_2-x_1}x+\frac{x_2y_1-x_1y_2}{x_2-x_1} y=x2−x1y2−y1x+x2−x1x2y1−x1y2
注意:
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 C: 直线
* 解决方案: 枚举。用两点式保存直线,枚举所有可能存在的直线,用set去重。
*/
#include
using namespace std;
// 两点确定一条直线
class Line {
int x1, y1, x2, y2;
public:
Line(int a, int b, int c, int d) : x1(a), y1(b), x2(c), y2(d) {
// 规范直线,保证(x2,y2)在(x1,y1)的右上方,方便比较截距大小
if (x2 < x1) swap(x1,x2), swap(y1,y2);
if (x1 == x2 && y1 > y2) swap(y1,y2);
}
bool operator < (const Line &t) const
{
// 首先根据斜率大小排序
// 斜率相等则按截距大小排序(注意斜率不存在的情况)
if ((y1-y2)*(t.x1-t.x2) != (x1-x2)*(t.y1-t.y2)) return (y1-y2)*(t.x1-t.x2) < (x1-x2)*(t.y1-t.y2);
if (x1 == x2) return x1 < t.x1; // 斜率不存在的情况下比较横截距
return (x2*y1-x1*y2)*(t.x2-t.x1) < (x2-x1)*(t.x2*t.y1-t.x1*t.y2); // 否则比较纵截距
}
};
int main()
{
int n = 20, m = 21;
// n = 2, m = 3;
set<Line> st;
// 数据范围很小,四层循环暴力枚举两点
for (int x1 = 0; x1 < n; ++x1) {
for (int y1 = 0; y1 < m; ++y1) {
for (int x2 = 0; x2 < n; ++x2) {
for (int y2 = 0; y2 < m; ++y2) {
if (x1 == x2 && y1 == y2) continue; // 保证两点不同
st.insert(Line(x1,y1,x2,y2));
}
}
}
}
// 集合的大小即为不同直线的数目
cout << st.size();
// 输出40257
return 0;
}
将 n n n 拆为 3 3 3 个正整数的乘积。
例如,当 n = 4 n = 4 n=4 时,有以下 6 种方案: 1 × 1 × 4 1×1×4 1×1×4、 1 × 2 × 2 1×2×2 1×2×2、 1 × 4 × 1 1×4×1 1×4×1、 2 × 1 × 2 2×1×2 2×1×2、 2 × 2 × 1 2 × 2 × 1 2×2×1、 4 × 1 × 1 4 × 1 × 1 4×1×1。
请问,当 n = 2021041820210418 n = 2021041820210418 n=2021041820210418 (注意有 16 16 16 位数字)时,总共有多少种
方案?
解决方案
先求出各 n n n 的所有质因子及其个数,问题便转化为: 将 n n n 的所有质因子放入三个不同的盒子,问有几种放法?
例如,当 n = 4 = 2 × 2 n=4=2\times2 n=4=2×2 时,两个相同的质因子放入三个不同的盒子,便有 C 3 1 + C 3 2 = 6 C_3^1+C_3^2=6 C31+C32=6 种。
注意到 2021041820210418 = 20210418 × 100000001 2021041820210418=20210418\times 100000001 2021041820210418=20210418×100000001 ,因此直接无脑暴力因式分解也不会超时。
那就先来因式分解一波:
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 D: 货物摆放
* 解决方案: 暴力出奇迹
*/
#include
using namespace std;
typedef long long LL;
void factors(LL x)
{
for (LL i = 2; i*i <= x; ++i) {
while (x%i == 0) x /= i, cout << i << ' ';
}
if (x > 1) cout << x;
}
int main()
{
LL n = 2021041820210418;
factors(n);
return 0;
}
// 输出: 2 3 3 3 17 131 2857 5882353
结果非常的 A mazing \text{{\color{red}A}\color{black}mazing} Amazing !
2021041820210418 2021041820210418 2021041820210418 居然只有 8 8 8 个质因子,遂手算即可。
对于两个不同的结点 a a a, b b b,如果 a a a 和 b b b 的差的绝对值大于 21 21 21,则两个结点
之间没有边相连;如果 a a a 和 b b b 的差的绝对值小于等于 21 21 21,则两个点之间有一条
长度为 a a a 和 b b b 的最小公倍数的无向边相连。
问结点 1 1 1 和结点 2021 2021 2021 之间的最短路径长度是多少?
解决方案
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 E: 路径
* 解决方案: 图论或dp
*/
#include
using namespace std;
const int N = 2022;
int dis[N][N]; // dp[i][j], 结点i到j的最短路
int main()
{
// 初始化
for (int i = 1; i < N; ++i) {
for (int j = 1; j < N; ++j) {
if (i == j) dis[i][j] = 0;
else if (abs(i-j) <= 21) dis[i][j] = i/__gcd(i,j)*j;
else dis[i][j] = INT_MAX/2;
}
}
for (int i = 1; i < N; ++i) {
for (int j = 1; j < N; ++j) {
for (int k = 1; k < N; ++k) {
if (dis[i][j] > dis[i][k]+dis[k][j]) {
dis[i][j] = dis[i][k]+dis[k][j];
}
}
}
}
cout << dis[1][2021]; // 10266837
return 0;
}
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 E: 路径
* 解决方案: 图论或dp
*/
#include
using namespace std;
const int N = 2022;
int dp[N];
int main()
{
dp[1] = 0;
for (int i = 2; i <= 2021; ++i) {
dp[i] = INT_MAX;
for (int j = max(1, i-21); j < i; ++j) {
dp[i] = min(dp[i], dp[j]+i/__gcd(i,j)*j);
}
}
cout << dp[2021]; // 10266837
return 0;
}
给定从 1970 1970 1970 年 1 1 1 月 1 1 1 日 00 : 00 : 00 00:00:00 00:00:00 到某时刻经过的毫秒数,要求输出该时刻对应的时分秒。
解决方案
时间复杂度 O ( 1 ) . O(1). O(1).
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 F: 时间显示
* 解决方案: 送分题
*/
#include
using namespace std;
typedef long long LL;
int main()
{
LL t;
cin >> t;
int h, m, s;
t /= 1000; s = t%60;
t /= 60; m = t%60;
t /= 60; h = t%24;
printf("%02d:%02d:%02d", h, m, s);
return 0;
}
出考场听见有人说不知道1s=1000ms,或许是大一新生吧。 \text{\color{#ccc}出考场听见有人说不知道1\text{s}=1000\text{ms},或许是大一新生吧。} 出考场听见有人说不知道1s=1000ms,或许是大一新生吧。
给定 n n n 个砝码,计算一共可以称出多少种不同的重量?
解决方案
注意到 n ≤ 100 n\le100 n≤100 且 砝码总重 M M M 不超过 100000. 100000. 100000.
考虑 d p i , j dp_{i,j} dpi,j 代表前 i i i 个砝码能否称出重量 j j j , 0 0 0 否 1 1 1 是,则有状态转移方程
d p i , j = d p i − 1 , j ∣ d p i − 1 , j − w ∣ d p i − 1 , j + w dp_{i,j}=dp_{i-1,j}|dp_{i-1,j-w}|dp_{i-1,j+w} dpi,j=dpi−1,j∣dpi−1,j−w∣dpi−1,j+w
注意:
时间复杂度 O ( n M ) . O(nM). O(nM).
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 G: 砝码称重
* 解决方案: 动态规划。每多一个砝码就标记哪些重量可以称出。
*/
#include
using namespace std;
const int N = 2e5+50, M = 1e5+5;
bool dp[2][N]; // dp[i][j]代表前i个砝码能否称出重量为j-M的物品,i用滚动数组形式
int main()
{
int n;
scanf("%d", &n);
dp[0][M] = 1; // 重量为0的物品肯定能称出
for (int i = 1; i <= n; ++i) {
int w;
scanf("%d", &w);
for (int j = 0; j < N; ++j) {
int cur = i&1, pre = cur^1;
// 加入第i个砝码后,能否称出重量j
// 取决于前i-1个砝码能否称出重量j或j-w或j+w
dp[cur][j] = dp[pre][j]
| (j-w >= 0 ? dp[pre][j-w] : false)
| (j+w < N ? dp[pre][j+w] : false);
}
}
int ans = 0; // 统计能称出的大于0, 及下标大于M的重量数
for (int i = M+1; i < N; ++i) {
ans += dp[n&1][i];
}
printf("%d", ans);
return 0;
}
给定正整数 n n n,求 n n n 在杨辉三角数列中第一次出现位置?
解决方案
如果纯粹暴力遍历求解,当给定数字过大时会超时。事实上,可以做一些“剪枝”操作。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 H: 杨辉三角形
* 解决方案: 模拟,剪枝。
*/
#include
using namespace std;
typedef long long LL;
const int N = 1e6;
LL C[2][N]; // 滚动数组存储杨辉三角相邻两行
int main()
{
int n = 99999999;
scanf("%d", &n);
int cnt = 0;
for (int i = 0; ; ++i) {
int cur = i&1, pre = cur^1;
for (int j = 0; j <= i; ++j) {
++cnt;
if (i == 0 || j == 0) C[cur][j] = 1;
else {
C[cur][j] = C[pre][j] + C[pre][j-1];
}
if (C[cur][j] == n) {
printf("%d", cnt);
return 0;
}
// 如果当前列超过n的最大值,那该行后面的列一定不会出现n
if (C[cur][j] > n) {
cnt += i-j;
break;
}
}
// 杨辉三角第三列大于n则说明数字n必在后面某行的第二列
if (C[cur][2] > n) break;
}
// 第n+1行第2列是第几个数?
cout << 1LL*n*(n+1)/2+2;
return 0;
}
容易验证,内层循环代码最多执行 18 18 18 万次,完全不用担心 TLE \text{TLE} TLE 。
给定序列 ( a 1 , a 2 , ⋅ ⋅ ⋅ , a n ) = ( 1 , 2 , ⋅ ⋅ ⋅ , n ) (a1, a2, · · · , an) = (1, 2, · · · , n) (a1,a2,⋅⋅⋅,an)=(1,2,⋅⋅⋅,n),即 a i = i ai = i ai=i。
小蓝将对这个序列进行 m m m 次操作,每次可能是将 a 1 , a 2 , ⋅ ⋅ ⋅ , a q i a_1, a_2, · · · , a_{qi} a1,a2,⋅⋅⋅,aqi 降序排列,或者将 a q i , a q i + 1 , ⋅ ⋅ ⋅ , a n a_{qi}, a_{qi+1}, · · · , a_n aqi,aqi+1,⋅⋅⋅,an 升序排列。
请求出操作完成后的序列。
解决方案
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 I: 双向排序
* 解决方案: 数据结构(线段树/文艺树)
*/
#include
using namespace std;
class SegTree {
vector<int> tree; // tree[rt]记录rt代表的区间中递增区间的个数,即1的个数
vector<int> lazy; // 代表将相应区间置为lazy, 初始化-1(无效值)
void pushUp(int rt) {tree[rt] = tree[rt<<1]+tree[rt<<1|1];}
void pushDown(int L, int R, int rt) {
if (lazy[rt] == -1) return;
int m = L+R >> 1;
tree[rt<<1] = lazy[rt]*(m-L+1);
tree[rt<<1|1] = lazy[rt]*(R-m);
lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
lazy[rt] = -1;
}
void build(int L, int R, int rt) {
if (L == R) {
tree[rt] = 1; // 初始化为n个数递增排列,全部标记为1。
return;
}
int m = L+R >> 1;
build(L, m, rt<<1);
build(m+1, R, rt<<1|1);
pushUp(rt);
}
public:
SegTree(int n) : tree(n << 2), lazy(n << 2, -1) {build(1, n, 1);}
void change0(int num, int L, int R, int rt) {
// 将最左的 num 个 0 变为 1
if (num <= 0) return;
if (R-L+1-tree[rt] <= num) { // 不足num个0直接置1
tree[rt] = R-L+1;
lazy[rt] = 1;
return;
}
pushDown(L, R, rt);
int m = L+R >> 1;
int t = min(m-L+1-tree[rt<<1], num);
change0(num-t, m+1, R, rt<<1|1);
change0(t, L, m, rt<<1); // 当前区间1的个数多于num则左递归
pushUp(rt);
}
void change1(int num, int L, int R, int rt) {
// 将最左的 num 个 1 变为 0
if (num <= 0) return;
if (tree[rt] <= num) { // 不足num个1直接置0
tree[rt] = 0;
lazy[rt] = 0;
return;
}
pushDown(L, R, rt);
int m = L+R >> 1;
int t = min(tree[rt<<1], num);
change1(num-t, m+1, R, rt<<1|1);
change1(t, L, m, rt<<1); // 当前区间1的个数多于num则左递归
pushUp(rt);
}
void print0(int L, int R, int rt) {
// 按递减顺序打印标记为0的数字
if (L == R) {
if (tree[rt] == 0) printf("%d ", L);
return;
}
pushDown(L, R, rt);
int m = L+R >> 1;
print0(m+1, R, rt<<1|1);
print0(L, m, rt<<1);
}
void print1(int L, int R, int rt) {
// 按递增顺序打印标记为1的数字
if (L == R) {
if (tree[rt] == 1) printf("%d ", L);
return;
}
pushDown(L, R, rt);
int m = L+R >> 1;
print1(L, m, rt<<1);
print1(m+1, R, rt<<1|1);
}
};
int main()
{
int n, m;
scanf("%d%d", &n, &m);
SegTree st(n);
int last_min = 1; // 记录上一次最小值所在的位置,初始化为1
int mark_min = 1; // 记录最小值是标的0还是1
for (int i = 1; i <= m; ++i) {
int p, q;
scanf("%d%d", &p, &q);
if (p == 0 && q > last_min) {
st.change1(q - last_min + (mark_min==1), 1, n, 1);
mark_min = 0;
last_min = q;
}
else if (p == 1 && q < last_min) {
st.change0(last_min - q + (mark_min==0), 1, n, 1);
mark_min = 1;
last_min = q;
}
}
st.print0(1, n, 1); // 降序打印标记为0的数字
st.print1(1, n, 1); // 升序打印标记为1的数字
return 0;
}
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,问有多少种本质不同的添加结果。
解决方案
动态规划。 d p i , j dp_{i,j} dpi,j 代表前在前 i i i 个字符构成的字符串中插入最少括号,使左括号比右括号多 j j j 个的方案数。
过 程 还 没 想 清 楚 , 改 天 补 。 。 \color{#aaa}过程还没想清楚,改天补。。 过程还没想清楚,改天补。。
估计我的得分情况应该是 5 + 5 + 10 + 0 + 15 + 15 + 10 → 20 ( 砝 码 称 重 用 的 set , 不 知 道 会 不 会 超 时 ) + 20 × 20 % + 0 = 79 → 89 5+5+10+0+15+15+10\to 20 (砝码称重用的\text{set},不知道会不会超时)+20\times 20\%+0=79\to 89 5+5+10+0+15+15+10→20(砝码称重用的set,不知道会不会超时)+20×20%+0=79→89 。
满分 150 150 150 , 89 89 89 分连及格线没到。
以前听很多人说蓝桥杯很水,甚至说有手就行。
但参加之后我才发现,蓝桥杯其实一点儿也不水,题目有深度、有细节,难度循序渐进,重点考察基本功。
当然,蓝桥杯获了奖也并不能说明问题,获奖除了能加德育分,没有什么值得高兴的,毕竟奖项是按本省相对排名分配的,而且有 60 % 60\% 60% 的获奖率。
每 参 加 一 次 比 赛 , 应 该 要 看 到 自 己 在 这 场 比 赛 中 暴 露 出 的 问 题 , 而 不 是 为 了 一 个 无 足 轻 重 的 结 果 而 喜 怒 哀 乐 , 这 样 才 能 有 所 提 升 。 \color{red}每参加一次比赛,应该要看到自己在这场比赛中暴露出的问题,\\而不是为了一个无足轻重的结果而喜怒哀乐\color{dark},这样才能有所提升。 每参加一次比赛,应该要看到自己在这场比赛中暴露出的问题,而不是为了一个无足轻重的结果而喜怒哀乐,这样才能有所提升。