文章目录
第一次参加蓝桥杯省赛,补题写博客留个纪念~
试题 A: 空间
问 256 M B 256MB 256MB 的空间可以存储多少个 32 32 32 位二进制整数。
>>> 256*1024*1024*8//32
67108864
试题 B: 卡片
小蓝手里有 0 0 0 到 9 9 9 的卡片各 2021 2021 2021 张,共 20210 20210 20210 张,问小蓝可以从 1 1 1 拼到多少。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 B: 卡片
* 解决方案: 模拟。记录各卡片剩余数量,每拼一个数字就减少相应的卡片,直到卡片不足。
*/
#include <bits/stdc++.h>
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;
}
试题 C: 直线
给定平面上 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
注意:
- 比较两直线的斜率和截距时建议不要用浮点类型(谨防浮点误差)。
- 存储时保证 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)位于 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),以方便比较直线大小。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 C: 直线
* 解决方案: 枚举。用两点式保存直线,枚举所有可能存在的直线,用set去重。
*/
#include <bits/stdc++.h>
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;
}
试题 D: 货物摆放
将 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 <bits/stdc++.h>
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 个质因子,遂手算即可。
- 个数为 1 1 1 的质因子放入三个不同的盒子,有 C 3 1 = 3 C_3^1=3 C31=3 种方案
- 个数为 1 1 1 的质因子放入三个不同的盒子,有 C 3 3 + 2 C 3 2 + C 3 1 = 10 C_3^3+2C_3^2+C_3^1=10 C33+2C32+C31=10 种方案
- 因此总方案数为 3 × 10 × 3 × 3 × 3 × 3 = 2430. 3\times10\times3\times3\times3\times3=2430. 3×10×3×3×3×3=2430.
试题 E: 路径
对于两个不同的结点
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 之间的最短路径长度是多少?
解决方案
- 方案一: 直接跑图论最短路, Dijkstra \text{Dijkstra} Dijkstra 或者 Floyd \text{Floyd} Floyd 均可,后者虽然时间复杂度高,但代码量少,且大概几十秒就能跑出来。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 E: 路径
* 解决方案: 图论或dp
*/
#include <bits/stdc++.h>
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;
}
- 方案二: 动态规划。
d
p
i
dp_i
dpi 代表从结点
1
1
1 到结点
i
i
i 之间的最短路径长度。状态转移方程为
d p i = min { d p j + lcm ( i , j ) } ( i − 21 ≤ j < i ) . dp_i=\text{min}\lbrace dp_{j}+\text{lcm}(i,j) \rbrace(i-21\le j\lt i). dpi=min{dpj+lcm(i,j)}(i−21≤j<i).
这是参照网上大佬的做法,但我并不理解。 这是巧合吗?为什么不用考虑从 j 之后的结点中转呢? \text{\color{red}这是参照网上大佬的做法,但我并不理解。}\\ 这是巧合吗?为什么不用考虑从j之后的结点中转呢? 这是参照网上大佬的做法,但我并不理解。这是巧合吗?为什么不用考虑从j之后的结点中转呢?
如果有人知道,还望告知,谢谢! \text{\color{#c6f}如果有人知道,还望告知,谢谢!} 如果有人知道,还望告知,谢谢!
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 E: 路径
* 解决方案: 图论或dp
*/
#include <bits/stdc++.h>
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;
}
试题 F: 时间显示
给定从 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 <bits/stdc++.h>
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,或许是大一新生吧。
试题 G: 砝码称重
给定 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
注意:
- i i i 的状态只与 i − 1 i-1 i−1 的状态有关,因此可以采用滚动数组,减少内存消耗。
- 运算过程中可能出现 j j j 为负数的情况,因此可映射到数组下标 j + M j+M j+M 的位置。
- 最终结果只用统计能称出的正的重量。
时间复杂度 O ( n M ) . O(nM). O(nM).
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 G: 砝码称重
* 解决方案: 动态规划。每多一个砝码就标记哪些重量可以称出。
*/
#include <bits/stdc++.h>
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;
}
试题 H: 杨辉三角形
给定正整数 n n n,求 n n n 在杨辉三角数列中第一次出现位置?
解决方案
如果纯粹暴力遍历求解,当给定数字过大时会超时。事实上,可以做一些“剪枝”操作。
- 如果某行某列大于 n n n 了还没出现,那么该行的该列之后的数也不会有 n n n 了。
- 特别的,如果某行第三列的数大于 n n n 还没出现,那么 n n n 必在接下来某行的第二列。由 C k 1 = n ⇒ k = n \text{C}_k^1=n\Rightarrow k=n Ck1=n⇒k=n 易知 n n n 将出现在第 n + 1 n+1 n+1 行第二列。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 H: 杨辉三角形
* 解决方案: 模拟,剪枝。
*/
#include <bits/stdc++.h>
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 。
试题 I: 双向排序
给定序列
(
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 升序排列。
请求出操作完成后的序列。
解决方案
- 方案一: 考场上只想到 O ( m n ) O(mn) O(mn) 的做法。注意到数组的值总是呈 V \text{V} V 型的,即先减后增。直接模拟 m m m 次排序,由于每次排序都只需合并两个有序数组,因此可以用双指针做到单次排序时间复杂度 O ( n ) O(n) O(n) 。
- 方案二: 线段树。考虑到数组总是先减后增,将所有单调递减的数标记为 0 0 0,递增的数标记为 1 1 1,则根据 01 01 01 序列便可唯一确定数组状态。而排序操作不过是将递减的数中较小的数变为递增的数(即将标记 0 0 0 改为 1 1 1),或者递增的数中较小的数变为递减的数(即将标记 1 1 1 改为 0 0 0),线段树区间修改时间复杂度 O ( log n ) O(\log n) O(logn) 。总体时间复杂度 O ( n + m log n ) O(n+m\log n) O(n+mlogn)。
/**
* 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
* 试题 I: 双向排序
* 解决方案: 数据结构(线段树/文艺树)
*/
#include <bits/stdc++.h>
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;
}
- 方案三: 伸展树。
试题 J: 括号序列
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,问有多少种本质不同的添加结果。
解决方案
任意给定一个括号序列 s s s
( ) ) ( ( ) ) ) ( ( ( ) ) ) ) ( ) ( ) ( ( ) ( ( ) ( ) ( ( ) ( ) ( ) ())(()))((())))()()(()(()()(()()() ())(()))((())))()()(()(()()(()()()
我们从中剔除所有的合法括号序列后,一定可以得到 ) ) ) ⋯ ) ( ( ( ⋯ ( \color{red})))\cdots)\color{blue}(((\cdots ( )))⋯)(((⋯( 的形式.
( ) ) ( ( ) ) ) ( ( ( ) ) ) ) ( ) ( ) ( ( ) ( ( ) ( ) ( ( ) ( ) ( ) \color{#eee} (){\color{red})}(()){\color{red})}((())){\color{red})}()(){\color{blue}(}(){\color{blue}(}()(){\color{blue}(}()()() ())(()))((())))()()(()(()()(()()()
记 ( ) ) ( ( ) ) ) ( ( ( ) ) ) ) \color{#eee}(){\color{red})}(()){\color{red})}((())){\color{red})} ())(()))((()))) 为 s 1 s_1 s1 , ( ( ) ( ( ) ( ) ( ( ) ( ) ( ) \color{#eee}{\color{blue}(}(){\color{blue}(}()(){\color{blue}(}()()() (()(()()(()()() 为 s 2 s_2 s2 .
根据乘法原理,将 s s s 变为合法序列的方案数等于将 s 1 , s 2 s_1,s_2 s1,s2 变为合法序列方案数的乘积。
那么如何求解 s 1 s_1 s1 这部分的方案数?记 f i , j f_{i,j} fi,j 为在将前 i i i 个字符变为合法字符串的基础上额外添加 j j j 个左括号的方案数,初始化为 f 0 , j = 1 f_{0,j} = 1 f0,j=1 ,则若 s 1 s_1 s1 的第 i i i (从 1 1 1 开始)个字符为左括号
f i , j = { 0 j = 0 , f i − 1 , j − 1 j ≥ 1. f_{i,j}=\begin{cases} 0&j=0,\\ f_{i-1,j-1}&j\ge 1. \end{cases} fi,j={0fi−1,j−1j=0,j≥1.
若 s 1 s_1 s1 的第 i i i (从 1 1 1 开始)个字符为右括号
f i , j = ∑ k = 1 j + 1 f i − 1 , k f_{i,j}=\sum\limits_{k=1}^{j+1}f_{i-1,k} fi,j=k=1∑j+1fi−1,k
f ∣ s 1 ∣ , 0 f_{|s_1|,0} f∣s1∣,0 即为 s 1 s_1 s1 的合法方案数,其中 ∣ s 1 ∣ |s_1| ∣s1∣ 代表 s 1 s_1 s1 的长度。(由于 f i f_i fi 的状态仅与 f i − 1 f_{i-1} fi−1 相关,因此可以将其压缩至一维。)
如何求解 s 2 s_2 s2 这部分的方案数?将 s 2 s_2 s2 左右镜像翻转得到 ( ) ( ) ( ) ) ( ) ( ) ) ( ) ) \color{#eee}()()(){\color{blue})}()(){\color{blue})}(){\color{blue})} ()()())()())()),记为 s 2 ′ s_2' s2′ . 不难发现 s 2 s_2 s2 的合法方案数与 s 2 ′ s_2' s2′ 相同,而 s 2 ′ s_2' s2′ 合法方案数的求解方法与 s 1 s_1 s1 一致。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
const int mod = 1e9 + 7;
string s;
cin >> s;
int ans = 1;
auto calc = [&] (char lb, char rb) {
int n = s.size();
vector<int> f(n + 1, 1);
int ans = 1;
for (int i = 0; s[i]; ++i) {
if (s[i] == lb) {
for (int i = f.size() - 1; i; --i) {
f[i] = f[i - 1];
}
f[0] = 0;
}
else {
for (int i = 0; i < f.size() - 1; ++i) {
f[i + 1] += i ? f[i] : 0;
f[i + 1] %= mod;
f[i] = f[i + 1];
}
}
f.pop_back();
if (f[0]) ans = f[0];
}
return ans;
};
ans = calc('(', ')');
reverse(s.begin(), s.end());
ans = 1LL * ans * calc(')', '(') % mod;
cout << ans << endl;
return 0;
}
赛后总结
估计我的得分情况应该是
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},这样才能有所提升。 每参加一次比赛,应该要看到自己在这场比赛中暴露出的问题,而不是为了一个无足轻重的结果而喜怒哀乐,这样才能有所提升。