2025-08-12 模拟赛总结 😅
预期:\(100+100+50+30=280\)。
实际:\(100+100+50+30=280\)。
排名:\(rk1/16\)。
比赛链接:https://htoj.com.cn/cpp/oj/contest/detail?cid=22463447766528&gid=22394193382400。
A - 异或 / xor:
题意:
定义 \(G(x,y)=2^x-2^y\),他给你了一个十六进制正整数 \(p\) 以及两个长度为 \(n\) 的序列 \(a,b\),而你需要求出以下表达式的值:\(p\oplus G(a_1,b_1)\oplus G(a_2,b_2)\oplus\cdots\oplus G(a_n,b_n)\)。
思路:
首先将 \(p\) 转化为二进制(有个技巧,可以将每一位转化为二进制然后拼接),我们发现 \(G(x,y)=2^x-2^y=\sum_{i=y}^{x-1}2^i\),即二进制上的 \(y\) 到 \(x-1\) 的位置上全部为 \(1\),那么异或 \(G(x,y)\) 相当于 \(y\sim x-1\) 的位置全部反转,这显然可以用差分做,最后在转回十六进制即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e6 + 5;
int n, q, l, r;
bool a[kMaxN], d[kMaxN];
string s, t;
int main() {
freopen("xor.in", "r", stdin);
freopen("xor.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> s, n = s.size();
for (int i = 0, j = 0; i < n; i++, j += 4) {
int x = '0' <= s[i] && s[i] <= '9' ? s[i] - '0' : s[i] - 'A' + 10;
for (int k = j + 3, l = 0; l < 4; k--, l++) {
a[k] = x >> l & 1;
}
}
d[1] = a[1];
for (int i = 1; i < 4 * n; i++) {
d[i] = a[i] ^ a[i - 1];
}
for (cin >> q; q; q--) {
cin >> l >> r;
l = 4 * n - l - 1, r = 4 * n - r - 1;
d[l + 1] ^= 1, d[r + 1] ^= 1;
}
a[1] = d[1];
for (int i = 1; i < 4 * n; i++) {
a[i] = a[i - 1] ^ d[i];
}
for (int i = 0; i < 4 * n; i += 4) {
int x = a[i] * 8 + a[i + 1] * 4 + a[i + 2] * 2 + a[i + 3];
if (0 <= x && x <= 9) {
t += x + '0';
} else {
t += x - 10 + 'A';
}
}
int pos = 0;
for (; t[pos] == '0'; pos++) {
}
cout << t.substr(pos);
return 0;
}
B - 01 transform / transform:
题意:
你可以对一个 01 串做如下操作:将相邻的 \(0\) 全变成 \(1\),将相邻的 \(1\) 全变成 \(0\)。
初始给定两个长度为 \(n\) 的 01 串 \(s\) 和 \(p\),你需要执行一些修改或询问。修改将 \(s\) 或 \(p\) 的某个区间反转,询问问是否能将 \(s[x,x+z-1]\) 通过操作变成 \(p[y,y+z-1]\)。
思路:
首先发现将相同的数同时反转不好做,除了异或和没有东西是定值,但是只看异或和又不行(如果任意两个数区间反转还是可以的),考虑 Gym-105484B 的结论,将奇数位反转,偶数位不变,那么题目就变成了将两个相邻且不同的数同时异或,这样其实就是交换两个相邻 \(01\) 的位置,那这样判断就好判了,因为这种操作不会改变 \(1\) 的数量,判断是否可以转化就是判断 \(1\) 的数量是否相同。区间反转,区间求和可以用线段树做。
注意一个小问题,如果 \(x,y\) 奇偶性相同就直接判断 \(1\) 的个数即可,否则要判断 \(1\) 的个数和是否为 \(z\)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e6 + 5;
int n, q;
string s, p;
struct SegmentTree {
#define mid (l + r >> 1)
int sum[kMaxN << 2], laz[kMaxN << 2];
void PushUp(int u) {
sum[u] = sum[u << 1] + sum[u << 1 | 1];
}
void AddTag(int u, int l, int r) {
sum[u] = r - l + 1 - sum[u], laz[u] ^= 1;
}
void PushDown(int u, int l, int r) {
if (laz[u]) {
AddTag(u << 1, l, mid);
AddTag(u << 1 | 1, mid + 1, r);
laz[u] = 0;
}
}
void Update(int u, int l, int r, int L, int R) {
if (L <= l && r <= R) return AddTag(u, l, r);
PushDown(u, l, r);
if (L <= mid) Update(u << 1, l, mid, L, R);
if (R > mid) Update(u << 1 | 1, mid + 1, r, L, R);
PushUp(u);
}
int Query(int u, int l, int r, int L, int R) {
if (L <= l && r <= R) return sum[u];
PushDown(u, l, r);
if (R <= mid) return Query(u << 1, l, mid, L, R);
if (L > mid) return Query(u << 1 | 1, mid + 1, r, L, R);
return Query(u << 1, l, mid, L, R) + Query(u << 1 | 1, mid + 1, r, L, R);
}
} ts, tp;
int main() {
freopen("transform.in", "r", stdin);
freopen("transform.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> q >> s >> p, s = ' ' + s, p = ' ' + p;
for (int i = 1; i <= n; i++) {
if ((i % 2) ^ (s[i] - '0')) {
ts.Update(1, 1, n, i, i);
}
if ((i % 2) ^ (p[i] - '0')) {
tp.Update(1, 1, n, i, i);
}
}
for (int o, x, y, z; q; q--) {
cin >> o >> x >> y;
if (o == 1) {
ts.Update(1, 1, n, x, y);
} else if (o == 2) {
tp.Update(1, 1, n, x, y);
} else {
cin >> z;
int ss = ts.Query(1, 1, n, x, x + z - 1);
int sp = tp.Query(1, 1, n, y, y + z - 1);
if (x % 2 == y % 2) {
cout << (ss == sp);
} else {
cout << (ss + sp == z);
}
}
}
return 0;
}
C - 森罗殿的光束交错 / light:
题意:
有 \(2n\) 个环状排布的带权点,第 \(i\) 个点权值为 \(d_i\),权值相同的两个点可以连一条边,使得边的交点最多,同时满足如下约束:
- 任意一个点的度最多为 \(1\),即最多连一条边。
- 一条边最多只能与一条边相交。
- 满足上述条件的图中必然有 \(2k\) 个点被连了边,应使得环上存在一个长度为 \(n\) 的连续区间,使得该区间内有且只有 \(k\) 个被连边的点,且它们互不连边。
思路:
看到多个条件,肯定先满足最严格的条件,最后一个条件看起来很难懂那就先满足最后一个条件,我们枚举这一段长度为 \(n\) 的连续区间,分成上下两个半圆,并将其拍扁到序列上变成序列 \(a,b\)。
我们现在需要将 \(a,b\) 的元素进行匹配,并满足前两个要求。前两个要求限制了什么,手玩一下发现就相当于如果匹配的元素连一条边,那么方案应该是一堆不相交的 X,这样转化这道题就好做了。
两个序列进行匹配可以考虑 dp,设 \(f_{i,j}\) 表示选到 \(a\) 序列的第 \(i\) 个位置的最大匹配数。如果不匹配那么 \(f_{i,j}\gets\max_(f_{i-1,j},f_{i,j-1})\),如果匹配,那就肯定尽量往后选,所以找到 \(i\) 之前的第一个满足 \(a_p=b_j\) 的数 \(p\),找到 \(j\) 之前的第一个满足 \(a_i=b_q\) 的数 \(q\),那么 \(f_{i,j}\gets f_{p-1,q-1}+1\)。然后对于每一个 \(i,j\),\(p,q\) 都可以预处理出来,加上枚举长度为 \(n\) 的连续区间的 \(O(n)\),这道题的时间复杂度:\(O(n^3)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 605;
int n, d[kMaxN * 2], a[kMaxN], b[kMaxN], ans, p[kMaxN][kMaxN], q[kMaxN][kMaxN], f[kMaxN][kMaxN];
int main() {
freopen("light.in", "r", stdin);
freopen("light.out", "w", stdout);
cin >> n;
for (int i = 1; i <= 2 * n; i++) {
cin >> d[i];
d[i + 2 * n] = d[i];
}
for (int k = 1; k <= n; k++, a[0] = b[0] = 0) {
for (int i = k; i <= k + n - 1; i++) a[++a[0]] = d[i];
for (int i = k + 2 * n - 1; i >= k + n; i--) b[++b[0]] = d[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
p[i][j] = a[i - 1] == b[j] ? i - 1 : p[i - 1][j];
q[i][j] = a[i] == b[j - 1] ? j - 1 : q[i][j - 1];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (p[i][j] && q[i][j]) f[i][j] = max(f[i][j], f[p[i][j] - 1][q[i][j] - 1] + 1);
}
}
ans = max(ans, f[n][n]);
}
cout << ans;
return 0;
}
D - 666 / 666:
题意:
定义 \(\operatorname{666}(x)\) 为 \(x\) 在十进制下数位的 \(6\) 的个数。现在给出一颗 \(n\) 个点的树,你需要求出:
思路:
看到这个式子,首先想到换根 dp,但是换根的过程距离的变动过于复杂,所以不能做。
接着考虑点分治,点分治后只需要考虑如何快速算出若干个子树之间的贡献。
对每一位考虑,如果一个数 \(x\) 想要第 \(i\) 位为 \(6\),那么使得 \(x+y\) 的第 \(i\) 位是 \(6\),那么 \(y\bmod 10^{i+1}\) 肯定属于 \([l_1,r_1]\cup[l_2,r_2]\),这两段区间的求法具体看代码,这样这道题就可以使用树状数组求解了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e5 + 5, kMaxV = 2e7 + 5;
int n, m, h[kMaxN], edgecnt = 1, dis[kMaxN], siz[kMaxN], rt, rtsz;
int p[kMaxN], tot, c[kMaxV], MAX = 1, stk[kMaxV], top;
bool vis[kMaxN], ins[kMaxV];
long long ans;
struct Edge {
int to, we, ne;
} e[kMaxN * 2];
void Add(int u, int v, int w) {
e[++edgecnt] = {v, w, h[u]}, h[u] = edgecnt;
}
void Dfs(int u, int fa, int S, int maxs = 0) {
siz[u] = 1;
for (int i = h[u], v; i; i = e[i].ne) {
v = e[i].to;
if (v != fa && !vis[v]) {
Dfs(v, u, S), siz[u] += siz[v];
maxs = max(maxs, siz[v]);
}
}
maxs = max(maxs, S - siz[u]), rtsz > maxs && (rtsz = maxs, rt = u);
}
int GetRoot(int u, int fa) { return (rtsz = 1e9, Dfs(u, fa, siz[u]), rt); }
void GetList(int u, int fa, int pre) {
dis[u] = dis[fa] + pre, p[++tot] = u;
for (int i = h[u], v, w; i; i = e[i].ne) {
v = e[i].to, w = e[i].we;
if (v != fa && !vis[v]) {
GetList(v, u, w);
}
}
}
void Modify(int x) {
for (x++; x <= MAX; x += x & -x) {
c[x]++, !ins[x] && (stk[++top] = x, ins[x] = 1);
}
}
int Query(int l, int r, int x = 0) {
l = max(l, 0), r = min(r, MAX);
if (l > r) return 0;
for (; r; r -= r & -r) x += c[r];
for (; l; l -= l & -l) x -= c[l];
return x;
}
void Clear() {
for (; top; c[stk[top]] = ins[stk[top]] = 0, top--) {
}
}
void Calc(int u, int fa) {
dis[u] = 0;
Modify(0);
for (int i = h[u], v, w; i; i = e[i].ne) {
v = e[i].to, w = e[i].we;
if (v != fa && !vis[v]) {
tot = 0, GetList(v, u, w);
for (int j = 1, l, r; j <= tot; j++) {
l = 6 * MAX / 10 - dis[p[j]] % MAX;
r = 7 * MAX / 10 - dis[p[j]] % MAX;
ans += Query(l, r) + Query(l + MAX, r + MAX); // 两端区间的求解
}
for (int j = 1; j <= tot; j++) {
Modify(dis[p[j]] % MAX);
}
}
}
Clear();
}
void Solve(int u, int fa) {
Calc(u, fa), vis[u] = 1;
for (int i = h[u], v; i; i = e[i].ne) {
v = e[i].to;
if (v != fa && !vis[v]) {
Solve(GetRoot(v, u), 0);
}
}
}
int main() {
freopen("666.in", "r", stdin);
freopen("666.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1, u, v, w; i < n; i++) {
cin >> u >> v >> w;
Add(u, v, w), Add(v, u, w);
}
for (int i = 1; i <= 7; i++) {
MAX *= 10;
siz[1] = n, Solve(GetRoot(1, 0), 0);
fill(vis, vis + 1 + n, 0);
}
cout << ans;
return 0;
}
总结:
这次的题目难度适中,前两道题做的速度刚好,但是后两题在虚空思考,T3 这种简单题我却因为题目太长最后才看,所以只写了暴力,而 T4 我想了很久但是没想出来。
以后不要因为题目太长或太难理解就跳过,有可能这道特别长的题目比较的简单。

浙公网安备 33010602011771号