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\) 个点的树,你需要求出:

\[\sum_{i=1}^n\sum_{j=i+1}^n\operatorname{666}(\operatorname{dis}(i,j)) \]

思路:

看到这个式子,首先想到换根 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 我想了很久但是没想出来。

以后不要因为题目太长或太难理解就跳过,有可能这道特别长的题目比较的简单。

posted @ 2025-08-12 19:49  liruixiong0101  阅读(240)  评论(0)    收藏  举报