2025.1.14 CW 模拟赛
T1
原题链接, 感觉没有紫.
算法
树状数组, 数学.
思路
若对于每一个矩形来考虑, 我们需要 \(\mathcal{O}(n^4)\) 地枚举每一个矩形, 没有太大的优化前途.
换个角度, 我们对于每一个点依次进行考虑. 如果该点是作为一个子集的一个元素, 那么这样的子集一共会有 \(2^{n - 1}\) 种 (因为该点必须选), 也就是每个点都会有 \(2^{n - 1}\) 次贡献. 再来考虑该点不在子集内却被矩形覆盖了的贡献.
如上图, 我们以红色的点为「原点」, 将坐标系内的点分为左上, 左下, 右上, 右下四个部分.
总共的选择方案有 \(\displaystyle (2^{cnt_l} - 1) \cdot (2^{cnt_r} - 1)\) 种. 考虑非法方案, 显然, 如果我们单单只选上半部分或者下半部分, 那么所形成的矩形一定覆盖不到红点. 减去非法的答案, 剩下的就是合法的方案了.
在实现上, 可以使用树状数组来维护四个部分点的个数, 代码里用的平衡树.
#include "bits/stdc++.h"
#include "bits/extc++.h"
using namespace std;
using namespace __gnu_pbds;
constexpr int N = 1e6 + 10, mod = 998244353;
int n, pw[N];
struct Point {
int x, y;
friend bool operator<(Point a, Point b) { return (a.x ^ b.x) ? a.x < b.x : a.y < b.y; }
} p[N];
void init() {
scanf("%d", &n);
pw[0] = 1;
for (int i = 1; i <= 4e5; ++i) pw[i] = pw[i - 1] * 2 % mod;
for (int i = 1; i <= n; ++i) scanf("%d %d", &p[i].x, &p[i].y);
sort(p + 1, p + n + 1);
}
tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> l, r;
void calculate() {
int ans = 1ll * n * pw[n - 1] % mod;
for (int i = 1; i <= n; ++i) r.insert(p[i].y);
for (int i = 1; i <= n; ++i) {
r.erase(p[i].y);
int ld = l.order_of_key(p[i].y), lu = l.size() - ld;
int rd = r.order_of_key(p[i].y), ru = r.size() - rd;
ans = (ans + 1ll * (pw[ld + lu] - 1) * (pw[rd + ru] - 1) - 1ll * (pw[rd] - 1) * (pw[ld] - 1) - 1ll * (pw[lu] - 1) * (pw[ru] - 1) + mod * 2) % mod;
l.insert(p[i].y);
}
printf("%d", ans);
}
void solve() {
init();
calculate();
}
signed main() {
solve();
return 0;
}
T2
算法
Ad-hoc, 感觉没什么算法.
思路
考虑先将两个询问子串中的 \(\rm{B}\) 全部转化成 \(\rm{AA}\).
注意到 \(\rm{A} \to BB \to AAAA \to A\), 也就是说其实一个长度为 \(\rm{len}\) 的子串所能够凑出的长度为 \(\rm{len} + 3 \times k, k \in \mathbb{Z}\). 将 \(\rm{len}_S, len_T\) (这里指将 \(\rm{B}\) 全部换成 \(\rm{AA}\) 后的长度) 作差, 如果结果能够被 \(3\) 整除, 那么就一定可以凑出, 否则输出 NO
.
void init() {
scanf("%s %s", s + 1, t + 1), read(q);
lens = strlen(s + 1), lent = strlen(t + 1);
for (int i = 1; i <= lens; ++i) ss[i] = ss[i - 1] + (s[i] == 'B');
for (int i = 1; i <= lent; ++i) st[i] = st[i - 1] + (t[i] == 'B');
}
void calculate() {
while (q--) {
int a, b, c, d, lt, ls;
read(a, b, c, d);
ls = b - a + 1, lt = d - c + 1;
ls += (ss[b] - ss[a - 1]), lt += (st[d] - st[c - 1]);
if ((lt - ls) % 3) puts("NO");
else puts("YES");
}
}
T3
算法
数学, 动态规划.
思路
搬运自SNCPC2019] Digit Mode - 洛谷专栏
令 \(a_i\) 表示 \(n\) 在十进制下从高到低的第 \(i\) 位, \(n\) 共有 \(cnt\) 位, \(b_i\) 为最终数在十进制下从高到低的第 \(i\) 位.
类似数位 DP
, 我们枚举前 \(x\) 位是「填满」了的 (\(\forall i \in [1, x], b_i = a_i\)), 然后第 \(x + 1\) 位满足 \(b_i < a_i\), 那么第 \(x + 2 \sim cnt\) 位就可以随便填了. 如果有前导 0, 我们枚举前 \(x\) 位是前导 0, 而第 \(x + 1\) 位填入一个正整数, 第 \(x + 2 \sim cnt\) 位也就随便填了. 设当前已经确定下来的有效数位中 \(i\) 的出现次数为 \(add_i\), 随便填的数位个数为 \(len\).
这时我们枚举出现次数最多的数是 \(m\), 其出现次数为 \(c\), 不难发现, 对于 \(x \ne m\), 在随便填的位置中最多出现 \(lim_x = c - add_x - [x > c]\) 次, 这其实类似于多重背包. 不妨设 \(f_{i, j}\) 表示前 \(i\) 个数 (不含 \(m\)), 一共用了 \(j\) 个数的方案数, 则有转移:
则最终对答案的贡献为 \(\displaystyle f_{9, len - (c - add_m)} \times c \times \binom{len}{c - add_m}\). 按照上述方法转移即可.
T4
算法
动态规划, 凸优化.
思路
先写出一个比较显然的 DP
. 不妨设 \(len_i = r_i - l_i\), \(f_{i, j}\) 表示枚举到了第 \(i\) 个矩形, 且当前矩形左端点在 \(j\) 的最小答案, 那么有转移方程 \(\displaystyle f_{i, j} = \min_{k = j - len_{i - 1}}^{j + len_i} f_{i - 1, k} + \lvert j - l_i \rvert\).
将 \(f_{i, j}\) 写成 \(f_i (j)\) (也就是一个函数), 可以证明其为凸函数, 同时 \(\lvert j - l_i \rvert\) 也是一个凸函数, 所以整体也是一个凸函数 (可以用二阶导数证明).
先来看 \(f_i(x)\), 分类讨论.
将 \(f_{i - 1}(x)\) 这个函数分成三段: 称这个 \(f_i(x)\) 的左边是斜率小于 0 的, 中间是斜率等于 0 的, 右边是斜率大于 0 的.
可以发现若 \(x\) 越靠近中间那一段, \(f_{i - 1}(x)\) 取值越小.
设 \(L, R\) 分别为 \(f_{i - 1}(x)\) 中间那一段的左右端点, 于是我们可以用上面的性质将 \(f_i(x)\) 的取值写成如下式子:
结合图片进行理解 (想了一个晚自习qwq).
通过取值范围, 可以发现每次相当于将 \(f_{i - 1}(x)\) 向左边平移 \(len_i\), 向右边平移 \(len_{i - 1}\), \(L \gets L - len_i, R \gets R + len_{i - 1}\).
然后我们需要加上 \(\lvert x - l_i \rvert\) 这个绝对值函数, 本质是将 \((-\infty, l_i)\) 这一整段函数斜率整体 \(-1\), \((l_i, +\infty)\) 这一段函数斜率整体 \(+1\). 具体来说, 我们分类讨论一下.
-
\(L \le l_i \le R\)
此时最小值只有一个位置 \(l_i\), 将 \(l_i\) 左侧斜率 \(-1\), 右侧斜率 \(+1\).
-
\(l_i < L\)
原来在 \(l_i\) 右侧的斜率为 \(-1\) 的这一段斜率会变成 \(0\). 设斜率为 \(-1\) 的这一段函数为 \([s, t]\), 令 \(s' = \max(s, l_i)\), 那么新的 \(L' = s', R' = t\), 原来 \(l_i\) 右侧的斜率全部减一, \([l_i, t]\) 的斜率全部加一, \(R'\) 右侧的斜率整体加一.
-
\(l_i > R\), 同 2 可得.
考虑使用两个堆 \(q_l, q_r\) 分别维护 \(L\) 左侧, \(R\) 右边的斜率拐点 (斜率 \(+1 / -1\) 的分界点). \(q_l\) 是大根堆, \(q_r\) 是小根堆, 所以 \(L, R\) 分别是 \(q_l,q_r\) 的堆顶.
int n, l[N], r[N], len[N];
priority_queue<int> ql;
priority_queue<int, vector<int>, greater<int>> qr;
void init() {
read(n);
for (int i = 1; i <= n; ++i)
read(l[i], r[i]), len[i] = r[i] - l[i];
ql.push(l[1]), qr.push(l[1]);
}
void calculate() {
int ans = 0;
for (int i = 2, tgl = 0, tgr = 0; i <= n; ++i) {
tgl -= len[i], tgr += len[i - 1];
int L = ql.top() + tgl, R = qr.top() + tgr;
if (l[i] < L) {
ans += L - l[i];
ql.pop(), ql.push(l[i] - tgl), ql.push(l[i] - tgl), qr.push(L - tgr);
}
else if (l[i] > R) {
ans += l[i] - R;
qr.pop(), qr.push(l[i] - tgr), qr.push(l[i] - tgr), ql.push(R - tgl);
}
else
ql.push(l[i] - tgl), qr.push(l[i] - tgr);
}
print(ans);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现