序言
生活不易,锦鲤无语。
其实个人感觉 『还好』,不至于完全没思路。
(投放 My blogs😃。)
来吧,上题!(对题有疑问可以直接问)
A. 零钱兑换
Give me your money!!1
「我的做题历程」:
step1:观察题面,这可以让我们了解题的类型。
「编写一个函数来计算可以凑成总金额」,可以得出这是一道背包 DP。
「每种硬币的数量是无限的」,进一步得出这是道完全背包。(题型:完全背包)
「最少的硬币个数」,证明这要在背包的前提下,求出最小组成数量。
「多组测试数据」,谨记多组输入 (论 Wrong Answer 与没有多组输入)。(注意:多组输入)
step2:思考解法。
第一步,思考 dp 状态:
d p i , j dp_{i,j} dpi,j:前 i i i 种硬币凑出面值 j j j 的最少币数。
对于当前一种硬币
c
o
i
n
s
i
coins_{i}
coinsi 而言,只有取或不取两种状态。
若取,取后的币数为前
i
−
1
i - 1
i−1 种硬币凑出面值
j
−
w
i
×
k
j-w_{i}\times k
j−wi×k 的总币数加上当前种类所需币数
k
k
k。
若不取,则说明前
i
−
1
i - 1
i−1 种硬币已经能够凑出面值
j
j
j,不需要再取。
第二步,思考状态转移方程:
原本完全背包的状态转移方程是:
d p i , j = max { d p i − 1 , j , d p i − 1 , j − a i + a i } ( a i ≤ j ≤ a m o u n t ) dp_{i, j} = \max\{dp_{i - 1, j}, dp_{i - 1, j - a_{i}}+a_{i}\}\ (a_{i}\le j\le amount) dpi,j=max{dpi−1,j,dpi−1,j−ai+ai} (ai≤j≤amount)
但这里我们并不是求总金额以内最大能凑出的面值,而是求凑成总金额的最少币数,于是就有:
d p i , j = min { d p i − 1 , j , d p i − 1 , j − a i + 1 } ( a i ≤ j ≤ a m o u n t ) dp_{i,j}=\min\{dp_{i - 1, j},\,dp_{i - 1, j - a_{i}} + 1\}\ (a_{i}\le j\le amount) dpi,j=min{dpi−1,j,dpi−1,j−ai+1} (ai≤j≤amount)
通过观察发现,上述方程可以降维。由于对
d
p
i
dp_{i}
dpi 有影响的只有
i
−
1
i - 1
i−1,故可以把前一维抹掉,但需要保证
d
p
i
,
j
dp_{i,j}
dpi,j 可以被
d
p
i
,
j
−
a
i
dp_{i, j - a_{i}}
dpi,j−ai 影响(即
d
p
i
,
j
dp_{i,j}
dpi,j 被计算时
d
p
i
,
j
−
a
i
dp_{i, j - a_{i}}
dpi,j−ai 已经被算出),这才相当于物品
i
i
i 多次被放入背包,所以枚举当前面值
j
j
j 时要正序。
第三步,打出完全背包的代码,把状态转移方程换一下,于是本题的算法部分就完成啦:
for (int i = 1; i <= n; i++) {
for (int j = a[i]; j <= amount; j++) {
dp[j] = min(dp[j], dp[j - a[i]] + 1);
}
}
step3:完成代码:
通过数据范围可以发现,一种硬币的面额是可以比总金额大的,因此可以预处理浅浅优化一下(虽然没什么大的效果)。
因为找的是最小币数,所以 dp 数组要初始化成极大值,而前
0
0
0 种硬币凑成 面值
0
0
0 只需要
0
0
0 种硬币,由此可得
d
p
0
=
0
dp_{0} = 0
dp0=0。
输出时值得注意的是,「如果没有任何一种硬币组合能组成总金额,输出
−
1
-1
−1」;在代码中,这意味着「如果
d
p
a
m
o
u
n
t
dp_{amount}
dpamount 没有被更新,则输出
−
1
-1
−1」,所以只需要输出时特判一下
d
p
a
m
o
u
n
t
dp_{amount}
dpamount 若仍是初始值就输出
−
1
-1
−1。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5, A = 1e4 + 5, INF = 0x3f3f3f3f;
int n, amount, a[N], dp[A];
/*
dp(i, j): 前 i 个硬币凑出 j 的最少硬币个数
dp(i, j) = min(dp(i - 1, j - a[i]), dp(i - 1, j));
取这个硬币 or 不取这个硬币
*/
int main() {
freopen("exchange.in", "r", stdin);
freopen("exchange.out", "w", stdout);
while (~scanf("%d %d", &n, &amount)) {
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = a[i]; j <= amount; j++) {
dp[j] = min(dp[j - a[i]] + 1, dp[j]);
}
}
printf("%d\n", dp[amount] == INF ? -1 : dp[amount]); // 可以使用三目运算符来特判
}
return 0;
}
Accepted 交换给我吧!!1
B. 蝙蝠侠的麻烦
没 事 找 事
「我的做题历程」:
step1:观察题面。
「蝙蝠侠需要找到一个最长的字符串,使得这个字符串作为一个子序列被包含在所有的三个字符串中」,可以得出这是一道最长公共子序列,而且有三个字符串。(题型:线性 dp —— 最长公共子序列)
「蝙蝠侠现在需要找到的是最大的长度,而不是序列」,说明只是一道普通的 LCS。
step2:思考解法。
第一步思考 dp 状态:
d p i , j , k dp_{i,j,k} dpi,j,k:第一串前 i i i 项,第二串前 j j j 项,第三串前 k k k 项中的最长公共子序列长度。
对于当前的
a
i
,
b
j
,
c
k
a_{i}, b_{j}, c_{k}
ai,bj,ck 而言,只有能做贡献或无法做贡献两种状态。
若
a
i
=
b
j
=
c
k
a_{i}= b_{j}= c_{k}
ai=bj=ck,则它们能做贡献,此时的最长公共子序列的长度为第一串前
i
−
1
i - 1
i−1 项,第二串前
j
−
1
j - 1
j−1 项,第三串前
k
−
1
k - 1
k−1 项中的最长公共子序列的长度加
1
1
1。
否则它们无法做贡献,此时放弃做贡献最小的那一项。
第二步思考状态转移方程:
本题比常规的 LCS 要多一个字符串,因此只需要再多一维就好。
d
p
i
,
j
,
k
=
d
p
i
−
1
,
j
−
1
,
k
−
1
+
1
(
a
i
=
b
j
=
c
k
)
dp_{i, j, k} = dp_{i - 1, j - 1 ,k - 1} + 1\ (a_{i} = b_{j} = c_{k})
dpi,j,k=dpi−1,j−1,k−1+1 (ai=bj=ck)
d
p
i
,
j
,
k
=
max
{
d
p
i
−
1
,
j
,
k
,
d
p
i
,
j
−
1
,
k
,
d
p
i
,
j
,
k
−
1
}
(
a
i
≠
b
j
or
b
j
≠
c
k
or
a
i
≠
c
k
)
dp_{i, j, k} = \max\{dp_{i - 1, j ,k},dp_{i, j - 1 ,k},dp_{i, j ,k - 1}\} \ (a_{i} \ne b_{j} \text{ or } b_{j} \ne c_{k} \text{ or }a_{i} \ne c_{k})
dpi,j,k=max{dpi−1,j,k,dpi,j−1,k,dpi,j,k−1} (ai=bj or bj=ck or ai=ck)
step3:完成代码:
因为有三个字符串,所以需要比平常的 LCS 多一层循环。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 5e1 + 5;
char a[N], b[N], c[N];
int la, lb, lc, dp[N][N][N];
/*
dp(i, j, k): 前 i, j, k 项中的最长公共子序列
if a(i) = b(j) = c(k)
dp(i, j, k) = dp(i - 1, j - 1, k - 1) + 1;
else
dp(i, j, k) = max{dp(i - 1, j, k), dp(i, j - 1, k), dp(i, j, k - 1)};
*/
int main() {
freopen("trouble.in", "r", stdin);
freopen("trouble.out", "w", stdout);
scanf("%s\n%s\n%s", a + 1, b + 1, c + 1);
la = strlen(a + 1), lb = strlen(b + 1), lc = strlen(c + 1);
for (int i = 1; i <= la; i++) {
for (int j = 1; j <= lb; j++) {
for (int k = 1; k <= lc; k++) {
if (a[i] == b[j] && b[j] == c[k]) {
dp[i][j][k] = dp[i - 1][j - 1][k - 1] + 1;
} else {
dp[i][j][k] = max(dp[i - 1][j][k], max(dp[i][j - 1][k], dp[i][j][k - 1]));
}
}
}
}
printf("%d", dp[la][lb][lc]);
return 0;
}
Trouble is a friend, but Accepted is better than it!
C. 带分数
数学老师:你这是最简分数吗你!
这道是深搜,平均用时
3000
m
s
3000\mathrm{ms}
3000ms;个人做法是暴力枚举,用时
139
m
s
139\mathrm{ms}
139ms。
但是我不知道这个同学 (变态)
130
m
s
130\mathrm{ms}
130ms 是怎么做到的……
「简述正解(鸣谢 GM 老师)」:
先把
1
∼
9
1\sim 9
1∼9 的全排列存在
a
a
a 数组中,再将
a
a
a 数组中的
9
9
9 个数分为
3
3
3 段,
m
1
=
sum
(
1
,
i
)
m_1 = \operatorname{sum}(1,i)
m1=sum(1,i)(
1
∼
i
1\sim i
1∼i 区间表示的数) 表示整数(当
m
1
≤
n
u
m
m_1\le num
m1≤num 时不必再算下去)。
i
+
1
∼
9
i + 1\sim 9
i+1∼9 要分为两段,枚举中间点
k
k
k,
m
2
=
sum
(
i
+
1
,
k
)
,
m
3
=
sum
(
k
+
1
,
9
)
m_2 = \operatorname{sum}(i + 1, k), m_3 = \operatorname{sum}(k + 1, 9)
m2=sum(i+1,k),m3=sum(k+1,9)。
if (m2 > m3 && m2 % m3 == 0 && num == m1 + m2 / m3) { // 判断是否为解
printf("%d=%d+%d/%d\n", n, m1, m2, m3);
ans++;
}
「我的做题历程」:
step1:观察题面。
「带分数中,数字
1
∼
9
1\sim 9
1∼9 分别出现且只出现一次(不包含
0
0
0),输出该数字
N
N
N 用数码
1
∼
9
1\sim 9
1∼9 不重复不遗漏地组成带分数表示的全部种数」,先闪出的是深搜,但由于想不出怎么搜,转战暴力。(题型:DFS or 暴力枚举)\
step2:思考解法。
抓住带分数构成特点:整数+假分数,即
x
=
a
+
b
c
(
a
,
b
,
c
≠
0
)
x = a + {b\over c}\ (a,b, c \ne 0)
x=a+cb (a,b,c=0)(假分数可化简为整数,即分子可以整除分母
(
b
=
k
c
,
c
∣
b
)
(b = kc,\ c\mid b)
(b=kc, c∣b) )。
于是想到先枚举前面的整数
a
a
a,然后得到后面的假分数化简的结果
k
k
k,通过枚举
c
c
c 来得到
b
b
b。再判断
a
,
b
,
c
a,b,c
a,b,c 是否满足题意即可。
总不能无限枚举吧,来估范围。分子最小为
1
1
1,则整数部分最小为
2
2
2,分母最大可取
9876543
9876543
9876543,还能得到当数
N
N
N 大于
9876545
9876545
9876545 时无解(然而
N
≤
1
0
6
N \le 10^6
N≤106 ,这个
N
N
N 的范围毫无用处)。
step3:完成代码。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, sum;
bool vis[11]; // 用于判断数码是否重复、遗漏
inline bool nosame(int x) { // 判断一个数的数码是否重复
memset(vis, 0, sizeof vis);
while (x != 0) {
if (vis[x % 10] || x % 10 == 0) {
return false;
}
vis[x % 10] = true;
x /= 10;
}
return true;
}
inline int len(int x) { // 获取该数位数(长度)
int l = 0;
while (x != 0) {
l++;
x /= 10;
}
return l;
}
inline bool nopublic(int a, int b, int c) { // 判断带分数中有无重复的数码
memset(vis, 0, sizeof vis);
while (a != 0) {
vis[a % 10] = true;
if (a % 10 == 0) {
return false;
}
a /= 10;
}
while (b != 0) {
if (vis[b % 10] || b % 10 == 0) {
return false;
}
vis[b % 10] = true;
b /= 10;
}
while (c != 0) {
if (vis[c % 10] || c % 10 == 0) {
return false;
}
vis[c % 10] = true;
c /= 10;
}
return true;
}
inline void get(int x) {
int l = len(n - x);
// 枚举倍数
for (int i = 1; i <= 9876543; i++) { // 分子至少为一,整数部分就只能为二,分母最大可取 9876543
if (nosame(x * i) && nosame(i) && nopublic(x * i, i, n - x) && len(x * i) + len(i) + l == 9) {
printf("%d=%d+%d/%d\n", n, n - x, x * i, i);
sum++;
}
if (len(x * i) + len(i) + l > 9) { // 如果数码大于 9 位就不用再算了
break;
}
}
return;
}
int main() {
freopen("fraction.in", "r", stdin);
freopen("fraction.out", "w", stdout);
while (~scanf("%d", &n)) {
sum = 0;
for (int i = 1; i < n; i++) {
if (nosame(i)) {
get(n - i);
}
}
printf("%d\n", sum);
}
return 0;
}
数据 Hack 了呢~你的 Accepted 被 Hack 掉了吗~( ̄y▽, ̄)╭
D. 锦鲤序列
锦鲤:为何如此之晦
「我的做题历程」:
step1:观察题面。
「锦鲤序列的长度一定是奇数,前
n
+
1
n+1
n+1 个数一定是严格单调递增的,后
n
+
1
n+1
n+1 个数一定是严格单调递减的,找到这个整数序列中最长的锦鲤序列」 ,明显的,该题是最长上升子序列的应用。(题型:线性 dp —— 最长上升子序列)
step2:思考解法。
第一步,思考 dp 状态:
d p 1 i dp1_{i} dp1i:从左至右找到分界点 i i i (可以不包括 i i i 点)的最长上升子序列长度。
d p 2 i dp2_{i} dp2i:从左至右找到分界点 i i i (可以不包括 i i i 点)的最长上升子序列长度。
第二步,思考答案位置:
由于锦鲤序列两端对称,且长度为奇数,所以对于每一个
i
i
i ,
a
n
s
=
max
{
a
n
s
,
2
×
min
{
d
p
1
i
,
d
p
2
i
}
−
1
}
ans = \max\{ans, 2 \times \min \{dp1_i,dp2_i\} - 1\}
ans=max{ans,2×min{dp1i,dp2i}−1}。
Q1:如果是 O ( n log n ) O(n\log n) O(nlogn) 的算法, i i i 点不一定被算过两次,那为什么一定要 − 1 -1 −1?
A:
请看下方示意图。记当前分界点为 i i i,两边的最长上升子序列为 f 1 , f 2 f1, f2 f1,f2 , f 1 = { a , b } ( a < b ) f1 = \{a, b\}\ (a\lt b) f1={a,b} (a<b), f 2 = { c , d , e } ( e < d < c ) f2 = \{c, d, e\}\ (e\lt d\lt c) f2={c,d,e} (e<d<c) 。
若两个子序列都包含 i i i ,那 − 1 -1 −1 是无疑的。当出现如下情况时,由于锦鲤序列两端对称,所以会舍弃掉 f 2 f2 f2 数组的一个元素。不妨假设舍弃元素 e e e ,剩下的数长度为偶数,不符题意,所以要再舍弃一个。
不妨假设在 b , c b, c b,c 中舍弃一个。若 b = c b=c b=c ,舍弃哪个都无所谓;若 b > c b > c b>c ,则一定能舍去 c c c(若 b > c b > c b>c ,则 b > d b > d b>d);同理,若 b < c b < c b<c ,则一定能舍去 b b b。
所以无论 i i i 点是否被算过,最后都要 − 1 -1 −1。
Q2:上图,如果 c c c 大于 b b b,则答案应该是 5 5 5,可算出来 3 3 3 不就错了吗?
A:
若 c c c 大于 b b b ,则长度确实该是 5 5 5 ,但这样看就忽略了一点, i i i 会枚举 1 ∼ n 1\sim n 1∼n 的每一个元素,当 i i i 枚举到 c c c 时,数组划分成 f 1 = { a , b , c } , f 2 = { c , d , e } f1 = \{a,b,c \},\ f2 = \{c,d,e\} f1={a,b,c}, f2={c,d,e},答案会更新到 5。因此对于一段代码绝不能就局部而纠结,要从宏观来观察它的作用。
step3:完成代码。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], dp1[N], dp2[N], f1[N], f2[N], ans;
int main() {
freopen("koi.in", "r", stdin);
freopen("koi.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
int len = 0;
dp1[0] = dp2[n + 1] = -1e9;
for (int i = 1; i <= n; i++) {
if (dp1[len] < a[i]) {
dp1[++len] = a[i];
}
dp1[lower_bound(dp1 + 1, dp1 + len + 1, a[i]) - dp1] = a[i];
f1[i] = len;
}
len = 0;
for (int i = n; i >= 1; i--) {
if (dp2[len] < a[i]) {
dp2[++len] = a[i];
}
dp2[lower_bound(dp2 + 1, dp2 + len + 1, a[i]) - dp2] = a[i];
f2[i] = len;
}
for (int i = 1; i <= n; i++) {
ans = max(ans, 2 * min(f1[i], f2[i]) - 1);
}
printf("%d", ans);
return 0;
}
还可以优化一下,快个
20
ms
20\text{ms}
20ms 左右。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], dp[N], dp2[N], s[N], ans;
int main() {
freopen("koi.in", "r", stdin);
freopen("koi.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
int len = 0;
dp[0] = dp[n + 1] = -1e9;
for (int i = 1; i <= n; i++) {
if (dp[len] < a[i]) {
dp[++len] = a[i];
}
dp[lower_bound(dp + 1, dp + len + 1, a[i]) - dp] = a[i];
s[i] = len;
}
len = 0;
for (int i = n; i >= 1; i--) {
if (dp2[len] < a[i]) {
dp2[++len] = a[i];
}
dp2[lower_bound(dp2 + 1, dp2 + len + 1, a[i]) - dp2] = a[i];
s[i] = min(s[i], len);
}
for (int i = 1; i <= n; i++) {
ans = max(ans, 2 * s[i] - 1);
}
printf("%d", ans);
return 0;
}
锦鲤保佑我拿到 Accepted
E. 烈焰
她从火光中走来,是那样璀璨夺目
这道 dp 惨遭吐槽:「这 dp 状态是正常人想的出来的吗?!」
烈焰(危)。
「我的做题历程」:
step1:观察题面。
“一维的扫雷地图” ,线性的。
“可能的情况有多少种” ,dp。
(题型:线性 dp)
step2:思考解法。
第一步思考 dp 状态:
d p i , j , k ( i ∈ [ 1 , n ] , j ∈ [ 0 , 1 ] , k ∈ [ 0 , 1 ] ) dp_{i, j, k}(i \in [1, n], j \in [0, 1], k \in [0, 1]) dpi,j,k(i∈[1,n],j∈[0,1],k∈[0,1]):前 i i i 位里,当前第 i i i 位和第 i + 1 i + 1 i+1 位是否有火的所有可能性,
第二步思考状态转移方程:
一共有四种可能性,
d
p
(
i
,
0
,
0
)
,
d
p
(
i
,
0
,
1
)
,
d
p
(
i
,
1
,
0
)
,
d
p
(
i
,
1
,
1
)
dp(i, 0, 0),\ dp(i, 0, 1),\ dp(i, 1, 0),\ dp(i, 1, 1)
dp(i,0,0), dp(i,0,1), dp(i,1,0), dp(i,1,1)。
然后根据题目写就好了,这里不展开。
step3:完成代码。
代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5, mod = 1e9 + 7;
typedef long long ll;
char s[N];
int dp[N][2][2], n;
int main() {
freopen("fire.in", "r", stdin);
freopen("fire.out", "w", stdout);
while (~scanf("%s", s + 1)) {
n = strlen(s + 1);
memset(dp, 0, sizeof dp);
dp[0][0][0] = dp[0][0][1] = 1;
for (int i = 1; i <= n; i++) {
if (s[i] == '0') {
dp[i][0][0] = dp[i - 1][0][0];
}
if (s[i] == '1') {
dp[i][0][1] = dp[i - 1][0][0];
dp[i][0][0] = dp[i - 1][1][0];
}
if (s[i] == '2') {
dp[i][0][1] = dp[i - 1][1][0];
}
if (s[i] == '*') {
dp[i][1][1] = dp[i][1][0] = (dp[i - 1][0][1] + dp[i - 1][1][1]) % mod;
}
if (s[i] == '?') {
dp[i][0][1] = dp[i][0][0] = (dp[i - 1][1][0] + dp[i - 1][0][0]) % mod;
dp[i][1][0] = dp[i][1][1] = (dp[i - 1][0][1] + dp[i - 1][1][1]) % mod;
}
}
printf("%d\n", (dp[n][1][0] + dp[n][0][0]) % mod);
}
return 0;
}
因为状态转移只牵扯到
i
i
i 和
i
+
1
i + 1
i+1 ,于是我们还能滚动一下,快个
800
ms
800\text{ms}
800ms 左右。
代码(抵制学术不端行为,拒绝 Ctrl + C):
// 滚动做法
#include <bits/stdc+
+.h>
using namespace std;
const int N = 1e6 + 5, mod = 1e9 + 7;
char s[N];
int dp[2][2][2], n;
int main() {
freopen("fire.in", "r", stdin);
freopen("fire.out", "w", stdout);
while (~scanf("%s", s + 1)) {
n = strlen(s + 1);
memset(dp, 0, sizeof dp);
dp[0][0][0] = dp[0][0][1] = 1;
for (int i = 1; i <= n; i++) {
if (s[i] == '0') {
dp[i & 1][0][0] = dp[i - 1 & 1][0][0];
dp[i & 1][1][0] = dp[i & 1][0][1] = dp[i & 1][1][1] = 0;
// 由于是滚动的所以不需要的数据无法被覆盖,需要自行清零
}
if (s[i] == '1') {
dp[i & 1][0][1] = dp[i - 1 & 1][0][0];
dp[i & 1][0][0] = dp[i - 1 & 1][1][0];
dp[i & 1][1][0] = dp[i & 1][1][1] = 0;
}
if (s[i] == '2') {
dp[i & 1][0][1] = dp[i - 1 & 1][1][0];
dp[i & 1][1][0] = dp[i & 1][0][0] = dp[i & 1][1][1] = 0;
}
if (s[i] == '*') {
dp[i & 1][1][1] = dp[i & 1][1][0] = (dp[i - 1 & 1][0][1] + dp[i - 1 & 1][1][1]) % mod;
dp[i & 1][0][0] = dp[i & 1][0][1] = 0;
}
if (s[i] == '?') {
dp[i & 1][0][1] = dp[i & 1][0][0] = (dp[i - 1 & 1][1][0] + dp[i - 1 & 1][0][0]) % mod;
dp[i & 1][1][0] = dp[i & 1][1][1] = (dp[i - 1 & 1][0][1] + dp[i - 1 & 1][1][1]) % mod;
}
}
printf("%d\n", (dp[n & 1][1][0] + dp[n & 1][0][0]) % mod);
}
return 0;
}
火中抢 Accepted
终于写完了
鸣谢 cqbzgm 提供的部分思路支持,C2024liuziming,C2024zhangwangbo,C2024liaoxindi 提供的格式指导🔆
补题完成~
将AC打包带走
将思路带包带走哦~
2022.5.14全盘题解到此结束~🎊🎊🎊
叭叭~(≧∇≦)ノ