2025-11-27 NOIP模拟赛总结

· · 生活·游记

前言

【MX-S5】梦熊 NOIP 2024 模拟赛 1(同步赛)

NOIP 前最后一场模拟赛了,是想让我在死前先把 rp 全都用光吗?

不好,后天就是 NOIP 了😵 希望至少做出一道题了,不然就真的变成 cutezrr 了。 ## A 非常 ez 的题目,看到从 $x$ 跳 $k$ 步,这一眼倍增好吧。 主要是看一些奇怪的细节,因为串是 $S^{\infty}$,所以倍增只能维护到的位置对 $n$ 取模的结果。🤔 这不简单,再维护一个经过段数的数量不就可以了。 设 $f_{i, j}, k_{i, j}$ 分别表示从位置 $i$ 跳 $2^j$ 步走到的位置(对 $n$ 取模)和经过点 $0$ 的次数,也就表示位置为 $k_{i, j} n + f_{i, j}$。 转移就非常板了 $$\begin{cases}f_{i, j} = f_{f_{i, j - 1}, j - 1}\\k_{i, j} = k_{i, j - 1} + k_{f_{i, j - 1}, j - 1}\end{cases}$$ 重点就在于初始化了,求出 $f_{i, 0}$ 与 $k_{i, 0}$ 是个问题😕 不过聪明的我一下子就想到了,预处理每一个点 $i$ 前面的最后一个 $1$ 的位置 $p_i$(若没有则记为 $-1$),每一个点做神秘分讨: 设 $x = p_{(i + m) \bmod n}$,即可以够到的最远点。 1. $x \not= -1$ 且 $x$ 不是 $i$ 本身,表示可以直接找到目标点,所以 $f_{i, 0} \gets x, k_{i, 0} \gets \lfloor\frac{i + m}{n}\rfloor$。 2. 无法直接找到目标,则若目标可以存在 $x$ 前一段的末尾,$f_{i, 0} \gets p_{n - 1}, k_{i, 0} \gets \lfloor\frac{i + m}{n}\rfloor - 1$。 3. 若不行,则 $f_{i, 0} \gets (i + 1) \bmod n, k_{i, 0} \gets \lfloor\frac{i + 1}{n}\rfloor$。 这样就可以愉快的做倍增了,记得取模,不然就是 zrr。代码中下标从 $0$ 开始,注意细节。还有个问题,总感觉少了点什么,没错!需要特判 $S = \texttt{0}^n$ 的情况,不然就会喜提 $95$ 分的好成绩: ![](https://cdn.luogu.com.cn/upload/image_hosting/t6evnojm.png) 耗时 40min 终于写出。 ::::success[T1 - 王国边缘] ```cpp #include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 2e5 + 5, M = 1e9 + 7; LL n, m, q, p[N], f[N][61], k[N][61]; string s; int main() { cin.tie(0)->sync_with_stdio(0); cin >> n >> m >> q >> s; if (!~s.find('1')) { for (LL x, l; q; q--) { cin >> x >> l, cout << (x + l) % M << "\n"; } exit(0); } for (int i = 0; i < n; i++) { p[i] = (s[i] == '1' ? i : (i ? p[i - 1] : -1)); } for (int i = 0; i < n; i++) { int x = p[(i + m) % n]; if (~x && (x != i || m >= n)) { f[i][0] = x, k[i][0] = (i + m) / n; } else if (i + m >= 2 * n - 1 || i + m >= n - 1 && i < p[n - 1]) { f[i][0] = p[n - 1], k[i][0] = (i + m) / n - 1; } else { f[i][0] = (i + 1) % n, k[i][0] = (i == n - 1); } } for (int j = 1; j < 61; j++) for (int i = 0; i < n; i++) { f[i][j] = f[f[i][j - 1]][j - 1]; k[i][j] = (k[i][j - 1] + k[f[i][j - 1]][j - 1]) % M; } for (LL x, y, l, s; q; q--) { cin >> x >> l, x--; y = x % n, s = x / n % M; for (LL i = 60; ~i; i--) { (1ll << i) <= l && (s = (s + k[y][i]) % M, y = f[y][i], l -= (1ll << i)); } cout << (s * n % M + y + 1) % M << "\n"; } return 0; } ``` :::: ps: 特判 $S = \texttt{0}^n$ 最开始判成了 $S = \texttt{1}^n$ 而且里面没有取模,大样例寄了,调了半天硬是没找到哪里错了😌。 ## B 水到不能再水了,返回贪心板子题啊,$15$ 分钟秒了> 感觉还没 T1 难(貌似是这样的)。 ::::success[T2 - 买东西题] ```cpp #include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 1e6 + 5; LL n, m, s; pair<LL, LL> a[N], v[N]; priority_queue<LL> q; int main() { cin.tie(0)->sync_with_stdio(0); cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> a[i].first >> a[i].second; } for (int i = 1; i <= m; i++) { cin >> v[i].first >> v[i].second; } sort(a + 1, a + n + 1); sort(v + 1, v + m + 1); for (int i = 1, j = 1; i <= n; i++) { auto [x, k] = a[i]; for (; j <= m && x >= v[j].first; j++) q.push(v[j].second); if (q.size() && x - q.top() < k) { s += x - q.top(), q.pop(), q.push(x - k); } else s += k; } cout << s; return 0; } ``` :::: ## C 挺有意思的一道题。 很容易发现,整个序列在操作过程中一定只有 $0, 1, 2$ 三个数,看 $\operatorname{popc}$ 表: |$\operatorname{popc}(x + y)$|$0$|$1$|$2$| |:-:|:-:|:-:|:-:| |$0$|$0$|$1$|$1$| |$1$|$1$|$1$|$2$| |$2$|$1$|$2$|$1$| 可貌似并不是很好做😞 还只想到了 $\mathcal{O}(n!)$ 的暴力,但马上又想到了 $\mathcal{O}(n^3)$ 求第一问的区间 DP。他那个求字典序最小的方案太烦人了,根本不知道如何处理。 突然发现 $a$ 中不含 $0$ 的特殊性质非常有趣,因为 $\operatorname{popc}(1 + 1) = 1, \operatorname{popc}(2 + 2) = 1, \operatorname{popc}(1 + 2) = 2$,也就是说当两数相同,答案为 $1$,否则答案为 $2$。注意到,无论如何操作,最终剩下的数是一个定值,所以模拟一遍求一下即可。字典序最小嘛,肯定是全对序列的开头操作。 很快就有 $27$ 分了,但就要止步于此了吗?还有两个多小时呢,感觉可以用一些骚操作把第一问求出来。惊人的注意力再次到来,当且仅当 $a$ 为 $11\cdots120211\cdots1$ 的形式时答案为 $2$,否则答案为 $1$。 ::::info[证明] 首先可以得到几个结论: + 因为没有 $0$ 的情况已经被判掉了,所以序列中一定有 $0$。 + 考虑将每个 $0$ 的左右两边进行合成,如果两边不都是 $2$,那么一定可以合出 $1$。 + 对于一个没有 $0$ 的序列,她的答案为 $2$ 的个数对 $2$ 取模的结果加一。 对此展开分类讨论: 1. $0$ 的数量超过一个,可以证明答案具有单调性,$0$ 的个数越多越可能合出 $1$,所以只讨论两个 $0$ 的情况。 序列 $a$ 一定形如 $A0B0C$,则又有两种情况: + $A, B, C$ 均为 $2$,那么这样操作:$20202 \to 2012 \to 212 \to 22 \to 1$。 + $A, B, C$ 不全为 $2$,那么先将所有的 $2$ 与 $0$ 凑成 $1$,最后就变为了一个没有 $2$ 的序列,答案一定为 $1$。 2. 只有一个 $0$: + 若 $2$ 的个数为偶数,那么判断 $0$ 的左右是否都是 $2$。如果有一个不是 $2$,那么和 $1$ 合并,达到目标;如果两个都是 $2$,因为序列不为 $11\cdots120211\cdots1$ 的形式,所以让所有 $2$ 先把 $1$ 干掉,$a$ 变为 $22\cdots2022\cdots2$ 的形式。至少有一边有至少两个 $2$,将最靠近 $0$ 的两个 $2$ 合出 $1$,再用 $1$ 将 $0$ 消掉,最终也变成了一个没有 $0$ 且 $2$ 的个数为偶数的序列。 + 若 $2$ 的个数为奇数,则让最靠近 $0$ 的那个二把中间的 $1$ 全部干掉,和 $0$ 合并变为 $1$,使 $2$ 的个数变为偶数。 所有情况全都讨论到了,证毕。 :::: 这个证明还是有点小复杂的啊,那么多情况,证了我半天。不过这样就可以拿到第一问的 $25$ 分,共计 $43$ 分😥 我真是太强了👍 ::::success[T3 - IMAWANOKIWA] ```cpp #include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int t, n, a[N], v; unsigned long long H; bool f[N]; array<int, N> o, w; string c; int C(int x) { return (x == 3 ? 2 : !!x); } void S(int x, vector<int> a) { if (x > n - 1) { if (a[0] == v && o < w) { w = o; for (int i = H = 0; i < n; i++) H = H * 13331 + o[i]; } return; } for (int i = 0; i + 1 < a.size(); i++) { vector<int> b = a; b.erase(b.begin() + i, b.begin() + i + 2); b.insert(b.begin() + i, C(a[i] + a[i + 1])); o[x] = i + 1, S(x + 1, b); } } int main() { cin.tie(0)->sync_with_stdio(0); for (cin >> t; t; t--) { cin >> c, n = c.size(), c = " " + c; bool l = 1, p = 1; for (int i = 1; i <= n; i++) { a[i] = c[i] - '0', l &= !!a[i], p &= !a[i]; } unsigned long long h = 0; for (int i = 2; i <= n; i++) h = h * 13331 + 1; if (p) { cout << "0 " << h << "\n"; } else if (l) { int s = a[1]; for (int i = 2; i <= n; i++) s = C(s + a[i]); cout << s << " " << h << "\n"; } else { fill(f + 1, f + n + 1, 0), f[n + 1] = 1; for (int i = n; i; i--) { f[i] = f[i + 1] & (a[i] == 1); } bool e = 1, g = 0; for (int i = 1; i + 2 <= n && e && !g; i++) { g |= a[i] == 2 && !a[i + 1] && a[i + 2] == 2 && f[i + 3]; e &= a[i] == 1; } v = g + 1; vector<int> z; for (int i = 1; i <= n; i++) z.push_back(a[i]); cout << v << " " << (n <= 10 ? w.fill(n + 1), S(1, z), H : 0) << "\n"; } } return 0; } ``` :::: 根本干不动了,这个结论太烧脑了,绝对不能继续在 T3 停留了。什么?只有 $10$ 分钟了?!这我玩你睿融啊,赶紧开 T4! ## D 5!4!3!2!1!最后一秒极限提交,什么叫真正的实力!你可不要小瞧这 $8$ 分了,因为要是没有这 $8$ 分我就……还是 rk1……那我可真的是要要小瞧这 $8$ 分了😰 没有什么时间了,只写了纯暴力,$\mathcal{O}(2^k nm(|S| + |T|))$。我是张蓉蓉,特殊性质 A 都没写,都怪 zrr。 ::::success[T4 - 魔法少女们] ```cpp #include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 5e5 + 5, M = 1e9 + 7; LL c, n, m, k, l, r; string s[N], t[N]; int main() { cin.tie(0)->sync_with_stdio(0); cin >> c >> n >> m >> k; for (int i = 1; i <= n; i++) cin >> s[i]; for (int i = 1; i <= m; i++) cin >> t[i]; for (int i = 0; i < (1 << k); i++) { l = 0; bool f = 1; for (int j = 0; j < k && f; j++) { ((i >> j) & 1) ? (l ? l-- : f = 0) : (l++); } if (!f || l) continue; string v = ""; for (int j = 0; j < k; j++) { v += ((i >> j) & 1) ? ")" : "("; } for (int x = 1; x <= n; x++) for (int y = 1; y <= m; y++) { r += v.substr(0, s[x].size()) == s[x] && v.substr(k - t[y].size()) == t[y]; } } cout << r; return 0; } ``` :::: ## 后记 最后一次模拟赛了,也希望后天 NOIP 正常发挥,rp++!