题解 LGP9266【[PA 2022] Nawiasowe podziały】/ SS240121A【Bracket】
- 题解 SS240122A【Bracket】
- 题解 QOJ5246【Nawiasowe podziały】
- 题解 LOJ3919【Nawiasowe podziały】
- 题解 LGP9266【[PA 2022] Nawiasowe podziały】
题目描述
花花和啾啾是好朋友。他们做完了切糕之后,打算一起切串。
一个括号串的权值是合法括号子串的数量。
现在他们有 \(k\) 要把一个长度为 \(n\) 的括号串 \(S\) 切成 \(k\) 个部分。他们组成了原串的划分。而划分方案的权值为每个部分的括号串权值之和。
花花和啾啾想知道,划分权值最小的方案的权值是多少呢?
\(1\leq k\leq n\leq 5\times 10^5\) ,不保证给定括号串合法。
solution
subtask 1
\(n\leq 300\)。
暴力 dp。输出答案数组可以发现答案关于划分段数是凸的,所以直接 wqs 二分可以 \(O(n^2\log n)\)。什么你问怎么算一个区间的合法括号串数?这里写一下我的赛时做法(支持区间滑动):
考虑维护 \(to_r\) 表示使得 \((l,r]\) 合法的最大的 \(l\)。发现,\(i\to to_i\) 这样连边后就剩下一堆链,链上一段区间就合法。每次例如从右边加入一个括号,就一直跳 \(to\),跳到的地方就是一个新的合法括号串。如果需要降低这部分复杂度,可以倍增,但和 dp 转移一样都是 \(O(n^2)\)。
\(O(1)\) 计算区间权值(笛卡尔树)
询问 \((L, R]\) 中有多少个合法括号子串。
对原括号串,(
为 \(+1\),)
为 \(-1\),做前缀和记为 \(s_i\),那么 \((l,r]\) 合法当且仅当 \(s_l=s_r=\min_{i=l}^rs_i\)。
对前缀和建广义笛卡尔树,每个节点代表一个区间 $ [l,r]$,然后拿着一个集合 \(S\),\(S\) 是 \([l,r]\) 中所有最小值的位置,这些位置将区间断开,又分为若干个区间(适当造一些空点),作为儿子。建树直接暴力 ST 表。如果在一个节点上 \(u,v\in S\),然后询问的 \((L,R]\) 有 \((u,v]\in(L,R]\)(注意开区间),那就有 \(1\) 的贡献。所以我们干这么一个事情,我们开始分类讨论。
节点 \([ l,r]\) 有着最小值位置集合 \(S\),对询问 \((L,R]\) 的贡献如下:
- 一类:\([l,r]\cap [L,R]=\varnothing\),\(0\)。
- 二类:\([L,R]\in[l,r]\),\(\dbinom{|S|}{2}\)。
- 三类:\(L\in[l,r],R\not\in[l,r]\),\(\dbinom{\sum_{x\in S}[x\geq L]}{2}\)。
- 四类:\(L\not\in[l,r],R\in[l,r]\),\(\dbinom{\sum_{x\in S}[x\leq R]}{2}\)。
- 五类:\(L,R\in[l,r]\),\(\dbinom{\sum_{x\in S}[L\leq x\leq R]}{2}\)。
这里的每一类贡献都要分的比较仔细,因为后面计算是要分类的。
考虑记 \(u\) 为节点,使得 \(S_{fa[u]}\) 中有 \(L\),\(u\) 就是 \(L\) 断开后左边的节点,可以提前记下;\(v\) 为节点,使得 \(S_{fa[v]}\) 中有 \(R\), \(v\) 就是 \(R\) 断开后右边的节点。记 \(w=\text{lca}(u, v)\)。暴力就是大概是,从 \(u\) 或者 \(v\) 开始跳父亲,然后每个父亲上就算到有多少个最小值能贡献,是 3 / 4 类贡献和旁边子树的 5 类贡献,最后在 \(w\) 上统计 2 / 5 类贡献。具体,记 \(f^k\) 表示 \(k\) 类贡献,\(F^k\) 是一些前缀和。\(F^2_u\) 表示 \(u\) 子树内所有节点的 \(\binom{|S|}{2}\) 的和。\(f^{3/4+5}_u\) 表示 \(u\) 点跳上父亲之后左边或者右边的最小值个数的 binom,加上旁边点的 5 类贡献。\(F^{3/4+5}_u\) 自然就是到根的前缀和。我的天这能算吗。好像真能。答案就是 \(F^{3+5}_u+F^{4+5}_v-F^{3+5}_x-F^{4+5}_y\) 还有一些,其中 \(x,y\) 是 \(u,v\) 跳到 \(w\) 的最后一个节点,剩下的就是 \(w\) 上做 \(F^2\) 的前缀和,以及记录 \(x,y\) 的排名算 \(f^5\)。
这个树上 \(k\) 级祖先是什么鬼啊,当然可以长剖,但是我们还可以记录欧拉环游序,ST 表查询完了以后,最左 LCA 的左和最右 LCA 的右就是 \(x,y\)。
综上可以 \(O(1)\) 计算区间权值。
\(O(1)\) 计算区间权值(猫树分治)
猫树分治就在建了前缀和,处理了前后缀答案后,我们就考虑跨过区间中点的,大概意思就是考虑左边后缀最小值和右边前缀最小值,在值域上扫下来,在值域上开前缀和数组计算即可。不想细节了。
subtask 4
\(k\leq 30\)。
明显满足四边形不等式,所以 dp 转移具有决策单调性。然后不用 wqs 直接做那种分治的决策单调性。\(O(nk\log n)\)。或者听说直接应用石子合并的东西可以 \(O(nk)\)?
考虑分治:假如我们已经有划分成 \(d-1\) 段的答案,欲求所有的 \(f_{i,d}\)。考虑欲求 \(f_{[L,R],d}\),现在有效的决策集合是 \([l,r]\)。
我们求出 \(f_{mid,d}\) 的值:用 \([l,r]\) 的决策更新。假如找到一个最优决策在 \(p\)。
那么我们断定:\([L,mid)\) 的点的决策为 \([l,p]\),\((mid,R]\) 的点的决策为 \([p,r]\)。
分析复杂度:一共 \(O(\log n)\) 层,每层平摊 \(O(n)\)。
我们的 \(V(l,r)\) 的复杂度如何分析?
考虑左右指针是独立的;考虑对于一个指针,在分治树上的每一个点恰好踩过一次,所以移动量为 \(O(n\log n)\)。
所以总复杂度为 \(O(n\log^2 n)\)。
subtask 5
\(n\leq 10^5\)。wqs 二分后选择喜欢的决策单调性方法维护(可以考虑队列维护三元组)。\(O(n\log^2n)\)。
可参考 https://www.luogu.com.cn/problem/P3515。
1D/1D 的 DP 中,维护队列 \((l,r,j)\) 表示 \(f_{i\in[l,r]}\) 都应该由 \(f_j\) 转移而来,\(j\leq l\)。队列中 \(j\) 单调,\([l,r]\) 顺次相连。
- 欲求 \(f_i\),那么检查队头 \(r_h<i\) 的 \((l_h,r_h,j_h)\) 的删掉。取出队头进行转移。\(l_h=i\)。
- 试图插入决策 \(i\) 的时候:
- 记队尾为 \((l_t,r_t,j_t)\)。
- 如果对于 \(f[l_t]\) 来说,\(i\) 优于 \(j_t\),删除队尾,\(pos=l_t\),返回第一步。
- 如果对于 \(f[r_t]\) 来说,\(j_t\) 优于 \(i\),\(pos=r_t+1\),去往第五步。
- 在 \([l_t,r_t]\) 中二分一个 \(pos\),使得 \(pos\) 往后是 \(i\) 更优,往前是 \(j_t\) 更优,去往第五步。
- 插入决策 \((pos,n,i)\)。
这样的总复杂度为 \(O(n\log n)\)。
(这里有一个误区就是做 \(f_i\) 时从 \(f_{i-1}\) 的决策点开始枚举,这样是假的,因为枚举 \([p_{i-1},i]\) 的时候,你拿到最优决策点,并不知道是最优决策点,只能继续枚举)
subtask 7(正解)
考虑先 wqs 二分。在笛卡尔树上 dp。到了区间 \([l,r]\),我们只考虑区间 \([l,r]\) 中切了 \(i_1,i_2,\cdots,i_k\),那么贡献就是
\(\text{cost}(l,r,S)\) 表示 \(calc(l,r)\) 只考虑 \(S\) 中的类别的贡献。
然后就是 \(u\) 只需要考虑 5 类贡献。然后就能 dp。然后就斜率优化。有点假,让我去写 2log 先。
code
2log,常数比较大
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
typedef long long LL;
template <int N, class T>
struct STable {
T f[21][N + 10];
int lg[N + 10], cnt;
STable() : cnt(0) { lg[0] = -1; }
int insert(T x) {
f[0][++cnt] = x;
lg[cnt] = lg[cnt >> 1] + 1;
for (int j = 1; 1 << j <= cnt; j++) {
int i = cnt - (1 << j) + 1;
f[j][i] = min(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
return cnt;
}
T query(int l, int r) {
int k = lg[r - l + 1];
return min(f[k][l], f[k][r - (1 << k) + 1]);
}
};
LL binom2(int n) { return 1ll * n * (n - 1) / 2; }
int n, K, m, a[500010];
char buf[500010];
STable<500010, pair<int, int>> Ta;
vector<int> g[1000010];
int id[500010];
bool cir[1000010];
int build(int l, int r) {
int u = ++m;
auto mn = Ta.query(l + 1, r + 1);
while (l <= r) {
auto ret = Ta.query(l + 1, r + 1);
if (ret.first != mn.first) {
g[u].push_back(build(l, r));
break;
}
int p = ret.second;
if (l < p) g[u].push_back(build(l, p - 1));
g[u].push_back(id[p] = ++m);
cir[id[p]] = true;
l = p + 1;
}
return u;
}
int rnk[1000010], fa[1000010], dep[1000010];
LL F2[1000010], F3[1000010], F4[1000010];
vector<int> pre[1000010];
vector<LL> pF2[1000010];
void dfs2(int u, int fa) {
::fa[u] = fa;
dep[u] = dep[fa] + 1;
pre[u] = vector<int>(g[u].size());
pF2[u] = vector<LL>(g[u].size());
for (int i = 0; i < g[u].size(); i++) {
rnk[g[u][i]] = i;
pre[u][i] = (i ? pre[u][i - 1] : 0) + cir[g[u][i]];
}
if (!pre[u].empty()) F2[u] = binom2(pre[u].back());
for (int v : g[u]) dfs2(v, u), F2[u] += F2[v];
for (int i = 0; i < g[u].size(); i++) {
pF2[u][i] = (i ? pF2[u][i - 1] : 0) + F2[g[u][i]];
}
}
void dfs34(int u) {
if (u > 1) {
F3[u] += F3[fa[u]] + binom2(pre[fa[u]].back() - (rnk[u] ? pre[fa[u]][rnk[u] - 1] : 0));
F4[u] += F4[fa[u]] + binom2(pre[fa[u]][rnk[u]]);
}
for (LL s = 0, i = 0; i < g[u].size(); i++) {
F4[g[u][i]] = s;
s += F2[g[u][i]];
}
for (LL s = 0, i = (int)g[u].size() - 1; i >= 0; i--) {
F3[g[u][i]] = s;
s += F2[g[u][i]];
}
for (int v : g[u]) dfs34(v);
}
int dfnf[1000010], dfnb[1000010];
int rnkf[1000010], rnkb[1000010];
STable<1000010, pair<int, int>> Tf, Tb;
void dfsf(int u) {
dfnf[u] = Tf.insert({dep[u], -(Tf.cnt + 1)});
rnkf[dfnf[u]] = u;
for (int i = 0; i < g[u].size(); i++) {
dfsf(g[u][i]);
}
}
void dfsb(int u) {
dfnb[u] = Tb.insert({dep[u], -(Tb.cnt + 1)});
rnkb[dfnb[u]] = u;
for (int i = (int)g[u].size() - 1; i >= 0; i--) {
dfsb(g[u][i]);
}
}
LL calc(int l, int r) {
//--l;
//(l, r]
assert(l < r);
int u = id[l], v = id[r];
int y = rnkf[-Tf.query(dfnf[u], dfnf[v]).second];
int x = rnkb[-Tb.query(dfnb[v], dfnb[u]).second];
int w = fa[x];
return F3[u] - F3[x] + F4[v] - F4[y]
+ binom2(pre[w][rnk[y]] - (rnk[x] ? pre[w][rnk[x] - 1] : 0))
- pF2[w][rnk[x]] + (rnk[y] ? pF2[w][rnk[y] - 1] : 0);
}
pair<LL, int> dp(LL ext) {
struct node {
int l, r, j;
};
static node q[500010];
static LL f[500010], g[500010];
int L = 1, R = 0;
q[++R] = {1, n, 0};
auto cmp = [&](int i, int j, int k) {
auto vi = f[i] + calc(i, k), vj = f[j] + calc(j, k);
return vi == vj ? g[i] > g[j] : vi < vj;
};
for (int i = 1; i <= n; i++) {
debug("i = %d\n", i);
while (L <= R && q[L].r < i) ++L;
assert(L <= R);
debug("choose j = %d\n", q[L].j);
f[i] = f[q[L].j] + calc(q[L].j, i) + ext;
g[i] = g[q[L].j] + 1;
while (L <= R && (q[R].l = max(q[R].l, i + 1)) && (q[R].l > q[R].r || cmp(i, q[R].j, q[R].l))) --R;
int pos = i;
if (L <= R) {
int l = q[R].l, r = q[R].r;
pos = r + 1;
for (int mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) {
if (cmp(i, q[R].j, mid)) pos = mid, r = mid - 1;
else l = mid + 1;
}
}
debug("pos = %d\n", pos);
if (pos <= n) q[R].r = pos - 1, q[++R] = {pos, n, i};
#ifdef LOCAL
for (int j = 0; j < i; j++) if (f[j] + calc(j, i) + ext < f[i]) f[i] = f[j] + calc(j, i) + ext, g[i] = g[j] + 1, debug("upd by %d\n", j);
else if (f[j] + calc(j, i) + ext == f[i] && g[i] < g[j] + 1) g[i] = max(g[i], g[j] + 1), debug("update by %d\n", j);
for (int j = L; j <= R; debug("[%d->%d]", q[j].l, q[j].r),j++) for (int t = q[j].l; t <= q[j].r; t++) debug("%d|", q[j].j);debug("\n");
#endif
}
return make_pair(f[n], g[n]);
}
LL binary(LL L, LL R) {
LL ans = -1;
for (LL mid = (L + R) >> 1; L <= R; mid = (L + R) >> 1) {
auto res = dp(mid);
debug("mid = %lld, res = {%d, %d}\n", mid, res.first, res.second);
if (res.second < K) R = mid - 1;
else L = mid + 1, ans = res.first - K * mid;
}
return ans;
}
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#endif
freopen("bracket.in", "r", stdin), freopen("bracket.out", "w", stdout);
cin >> n >> K >> buf;
for (int i = 1; i <= n; i++) a[i] = a[i - 1] + (buf[i - 1] == '(' ? +1 : -1);
for (int i = 0; i <= n; i++) Ta.insert({a[i], i});
build(0, n);
dfs2(1, 0);
dfsf(1), dfsb(1);
dfs34(1);
cout << binary(0, 1e12) << endl;
return 0;
}
1log 斜率优化正解
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
typedef long long LL;
template <int N, class T>
struct STable {
T f[21][N + 10];
int lg[N + 10], cnt;
STable() : cnt(0) { lg[0] = -1; }
int insert(T x) {
f[0][++cnt] = x;
lg[cnt] = lg[cnt >> 1] + 1;
for (int j = 1; 1 << j <= cnt; j++) {
int i = cnt - (1 << j) + 1;
f[j][i] = min(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
}
return cnt;
}
T query(int l, int r) {
int k = lg[r - l + 1];
return min(f[k][l], f[k][r - (1 << k) + 1]);
}
};
LL binom2(int n) { return 1ll * n * (n - 1) / 2; }
int n, K, m, a[500010];
char buf[500010];
STable<500010, pair<int, int>> Ta;
vector<int> g[1000010];
int id[500010];
bool cir[1000010];
int build(int l, int r) {
int u = ++m;
auto mn = Ta.query(l + 1, r + 1);
while (l <= r) {
auto ret = Ta.query(l + 1, r + 1);
if (ret.first != mn.first) {
g[u].push_back(build(l, r));
break;
}
int p = ret.second;
if (l < p) g[u].push_back(build(l, p - 1));
g[u].push_back(id[p] = ++m);
cir[id[p]] = true;
l = p + 1;
}
return u;
}
int rnk[1000010], fa[1000010], dep[1000010];
LL F2[1000010], F3[1000010], F4[1000010];
vector<int> pre[1000010];
vector<LL> pF2[1000010];
void dfs2(int u, int fa) {
::fa[u] = fa;
dep[u] = dep[fa] + 1;
pre[u] = vector<int>(g[u].size());
pF2[u] = vector<LL>(g[u].size());
for (int i = 0; i < g[u].size(); i++) {
rnk[g[u][i]] = i;
pre[u][i] = (i ? pre[u][i - 1] : 0) + cir[g[u][i]];
}
if (!pre[u].empty()) F2[u] = binom2(pre[u].back());
for (int v : g[u]) dfs2(v, u), F2[u] += F2[v];
for (int i = 0; i < g[u].size(); i++) {
pF2[u][i] = (i ? pF2[u][i - 1] : 0) + F2[g[u][i]];
}
}
void dfs34(int u) {
if (u > 1) {
F3[u] += binom2(pre[fa[u]].back() - (rnk[u] ? pre[fa[u]][rnk[u] - 1] : 0));
F4[u] += binom2(pre[fa[u]][rnk[u]]);
}
for (LL s = 0, i = 0; i < g[u].size(); i++) {
F4[g[u][i]] = s;
s += F2[g[u][i]];
}
for (LL s = 0, i = (int)g[u].size() - 1; i >= 0; i--) {
F3[g[u][i]] = s;
s += F2[g[u][i]];
}
for (int v : g[u]) dfs34(v);
}
/*
LL calc(int l, int r) {
//--l;
//(l, r]
assert(l < r);
int u = id[l], v = id[r];
int y = rnkf[-Tf.query(dfnf[u], dfnf[v]).second];
int x = rnkb[-Tb.query(dfnb[v], dfnb[u]).second];
int w = fa[x];
return F3[u] - F3[x] + F4[v] - F4[y]
+ binom2(pre[w][rnk[y]] - (rnk[x] ? pre[w][rnk[x] - 1] : 0))
- pF2[w][rnk[x]] + (rnk[y] ? pF2[w][rnk[y] - 1] : 0);
}
*/
struct dot {
LL x, y;
int fd;
bool operator<(const dot& b) const { return y != b.y ? y < b.y : fd > b.fd; }
dot operator-(dot b) const { return {x - b.x, y - b.y}; }
friend LL cross(dot a, dot b) { return a.x * b.y - a.y * b.x; }
};
template <int N>
struct convexHull {
dot q[N + 10];
int L, R;
void clear() { L = 1, R = 0; }
convexHull() { clear(); }
void addPoint(LL x, LL y, int fd) {
dot t = {x, y, fd};
while (L < R) {
auto ret = cross(t - q[R], q[R - 1] - q[R]);
if (ret) {
if (ret < 0) --R;
else break;
} else {
if (t.x != q[R].x) {
--R;
} else {
t = min(t, q[R--]);
t = min(t, q[R--]);
}
}
}
if (L == R && q[R].x == t.x) t = min(t, q[R--]);
q[++R] = t;
}
auto query(LL k) {
while (L < R && cross(q[L + 1] - q[L], {1, k}) > 0) ++L;
auto res = make_pair(q[L].y - k * q[L].x, q[L].fd);
if (L < R && !cross(q[L + 1] - q[L], {1, k})) res.second = max(res.second, q[L + 1].fd);
return res;
}
bool empty() { return L > R; }
};
bool must[2][1000010];
LL f[1000010], h[1000010];
int fd[1000010], fh[1000010];
void dp(int u, LL ext) {
f[u] = 1e18;
fd[u] = 0;
if (cir[u]) return f[u] = ext, fd[u] = 1, void();
for (int v : g[u]) dp(v, ext);
static convexHull<1000010> C;
C.clear();
for (int i = 0; i < g[u].size(); i++) {
if (must[0][u] && i) h[i] = 1e18, fh[i] = 0;
else h[i] = f[g[u][i]] + F4[g[u][i]], fh[i] = fd[g[u][i]];
/*
for (int j = 0; j < i; j++) {
auto tmp = h[j] + binom2(pre[u][i] - (j ? pre[u][j - 1] : 0)) + pF2[u][i - 1] - pF2[u][j] + f[g[u][i]];
debug("u = %d, i = %d, j = %d, tmp = %lld\n", u, i, j, tmp);
if (h[i] > tmp) h[i] = tmp, fh[i] = fh[j] + fd[g[u][i]];
else if (h[i] == tmp && fh[i] < fh[j] + fd[g[u][i]]) fh[i] = fh[j] + fd[g[u][i]];
*/
LL a = pre[u][i], b = i ? pre[u][i - 1] : 0;
// 2h[i] = 2h[j] + (a - b[j]) * (a - b[j] - 1) + 2pF2[u][i - 1] - 2pF2[u][j] + 2f[g[u][i]]
// (a - b[j]) * (a - b[j] - 1) = (a - b[j]) ^ 2 - a + b[j] = a ^ 2 - 2ab[j] + b[j] ^ 2 - a + b[j]
if (!C.empty()) {
auto ret = C.query(a);
ret.first += a * a - a + 2 * pF2[u][i - 1] + 2 * f[g[u][i]];
assert(ret.first % 2 == 0);
ret.first >>= 1;
ret.second += fd[g[u][i]];
if (h[i] > ret.first) h[i] = ret.first, fh[i] = ret.second;
else if (h[i] == ret.first) fh[i] = max(fh[i], ret.second);
}
if (h[i] < 1e18) C.addPoint(b * 2, 2 * h[i] + b * b + b - 2 * pF2[u][i], fh[i]);
if (!must[1][u] || i + 1 == g[u].size()) {
if (f[u] > h[i] + F3[g[u][i]]) f[u] = h[i] + F3[g[u][i]], fd[u] = fh[i];
else if (f[u] == h[i] + F3[g[u][i]] && fd[u] < fh[i]) fd[u] = fh[i];
}
}
debug("f[%d] = %lld, fd[%d] = %d\n", u, f[u], u, fd[u]);
}
auto dp(LL ext) {
debug("ext = %lld\n", ext);
dp(1, ext);
return make_pair(f[1] - ext, fd[1] - 1);
}
LL binary(LL L, LL R) {
LL ans = -1;
for (LL mid = (L + R) >> 1; L <= R; mid = (L + R) >> 1) {
auto res = dp(mid);
debug("mid = %lld, res = {%lld, %d}\n", mid, res.first, res.second);
if (res.second < K) R = mid - 1;
else L = mid + 1, ans = res.first - K * mid;
}
return ans;
}
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#ifndef NF
freopen("bracket.in", "r", stdin);
freopen("bracket.out", "w", stdout);
#endif
#endif
cin >> n >> K >> buf;
for (int i = 1; i <= n; i++) a[i] = a[i - 1] + (buf[i - 1] == '(' ? +1 : -1);
for (int i = 0; i <= n; i++) Ta.insert({a[i], i});
build(0, n);
dfs2(1, 0);
dfs34(1);
for (int p = id[0]; p; p = fa[p]) must[0][p] = true;
for (int p = id[n]; p; p = fa[p]) must[1][p] = true;
for (int i = 0; i <= n; i++) debug("id[%d] = %d\n", i, id[i]);
for (int i = 1; i <= m; i++) {
for (int v : g[i]) debug("(%d, %d)\n", i, v);
}
cout << binary(0, 1e12) << endl;
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/17980261/solution-SS240121A