2025-05-02 模拟赛总结 😥
预期:\(100+100+100+20=320\)。
实际:\(100+90+10+20=220\)。
排名:\(rk2/13\)。
比赛链接:http://yl503.yali.edu.cn/d/HEIGETWO/homework/6814656b4c37221ca63fe864。
A - WTP 的大洗牌 / shuffle:
题意:
给定 \(n\) 对数 \((a_i,b_i)\in\mathbb{Z}^2\),求出 \((x,y)\in\mathbb{Z}^2\),使得 \(x^2+y^2\equiv\displaystyle\prod_{i=1}^n(a_i^2+b_i^2)\pmod{2^{64}}\)。
思路:
利用拉格朗日恒等式:\((a^2+b^2)(c^2+d^2)=(ac+bd)^2+(ad-bc)^2\)。
每次合并两个数对 \((a_i,b_i)\) 和 \((a_{i+1},b_{i+1})\) 再 \(\bmod 2^{64}\),就做完了。
代码:
#include <bits/stdc++.h>
using namespace std;
using ULL = unsigned long long;
const int kMaxN = 5e5 + 5;
int n;
ULL a[kMaxN], b[kMaxN], x, y, tx, ty;
int main() {
freopen("shuffle.in", "r", stdin);
freopen("shuffle.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
x = a[1], y = b[1];
for (int i = 2; i <= n; i++) {
tx = x * a[i] + y * b[i];
ty = y * a[i] - x * b[i];
x = tx, y = ty;
}
cout << x << ' ' << y;
return 0;
}
B - WTP 的成神之路 / god:
题意:
现在有 \(n(1\le n\le 10^5)\) 个数 \(0=a_0\le a_1\le a_2\le\cdots\le a_n\),WTP 和小 B 在玩一个游戏,每次操作需要选择 \(1\le i\le n\),使得 \(a_i\gets x(a_{i-1}\le x\le a_i)\),哪一方不能操作哪一方输。每一次 WTP 可以在数列的两端插入新的数,小 B 想要知道每次操作后先手赢还是后手赢。
思路:
熟知,先手赢的充要条件为 \((a_n-a_{n-1})\oplus(a_{n-2}-a_{n-3})\cdots\neq 0\),直接维护奇数位和偶数位的差分异或值即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e6 + 5;
int T, n, q, ans1, ans2, x[kMaxN], minn, maxn;
int main() {
freopen("god.in", "r", stdin);
freopen("god.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
for (cin >> T; T; T--, ans1 = ans2 = 0) {
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> x[i];
minn = x[1], maxn = x[n];
for (int i = n; i >= 1; i -= 2) {
ans1 ^= x[i] - x[i - 1];
ans2 ^= x[i - 1] - x[i - 2];
}
cout << !ans1 << '\n';
for (int o, x; q; q--) {
cin >> o >> x;
if (o) {
ans2 ^= x - maxn, maxn = x;
swap(ans1, ans2);
} else {
if (n % 2) {
ans1 ^= minn ^ (minn - x);
ans2 ^= x;
} else {
ans2 ^= minn ^ (minn - x);
ans1 ^= x;
}
minn = x;
}
n++;
cout << !ans1 << '\n';
}
}
return 0;
}
反思:
也不知道怎么挂了 \(10pts\),但是以后不要再用 deque 了,常数过于大了。
C - WTP 的通缉 / wanted:
题意:
给定一颗 \(n(1\le n\le 10^5)\) 个点的带权树,每次询问给定 \(l_1,r_1,l_2,r_2\),你需要求出 \(\displaystyle\max_{l_1\le i\le r_1,l_2\le i\le r_2}\operatorname{dis}(i,j)\)。
思路:
一个经典结论是,\(\displaystyle\max_{i\in A,j\in B}\operatorname{dis}(i,j)\),取到最大值的两个点 \(i_{\max},j_{\max}\) 肯定分别为点集 \(A\) 的直径端点中的一个和点集 \(B\) 的直径端点中的一个。定义一个点集 \(A\) 的直径为 \(\displaystyle\max_{i,j\in A}\operatorname{dis}(i,j)\)。
题目就转化为了求任意一个区间内的直径端点。上面的结论告诉我们,将两个点集合并,直径的端点只可能是两个点集的四个直径端点中的两个,用线段树维护,暴力合并即可。
如果用 \(\mathcal{O}(\log n)\) 的 LCA 可能会挂,用欧拉序 \(\mathcal{O}(1)\) 求会更快。
代码:
#include <bits/stdc++.h>
using namespace std;
using Pii = pair<int, int>;
const int kMaxN = 1e5 + 5, kL = 20;
int n, q, dep[kMaxN], tot, f[kMaxN << 1][kL], p[kMaxN];
long long dis[kMaxN];
vector<Pii> g[kMaxN];
void Dfs(int u, int fa) {
f[p[u] = ++tot][0] = u;
for (Pii i : g[u]) {
int v = i.second, w = i.first;
if (v != fa) {
dep[v] = dep[u] + 1, dis[v] = dis[u] + w;
Dfs(v, u), f[++tot][0] = u;
}
}
}
int Min(int x, int y) { return dep[x] < dep[y] ? x : y; }
int Lca(int x, int y) {
int l = p[x], r = p[y];
if (l > r) swap(l, r);
int t = __lg(r - l + 1);
return Min(f[l][t], f[r - (1 << t) + 1][t]);
}
long long Dis(int x, int y) { return dis[x] + dis[y] - 2 * dis[Lca(x, y)]; }
struct T {
int l, r;
} t[kMaxN << 2], A, B;
T operator+(T a, T b) {
Pii t[6] = {{a.l, a.r}, {a.l, b.l}, {a.l, b.r}, {a.r, b.l}, {a.r, b.r}, {b.l, b.r}};
Pii x = *min_element(t, t + 6, [](Pii i, Pii j) { return Dis(i.first, i.second) > Dis(j.first, j.second); });
return {x.first, x.second};
}
void Build(int u, int l, int r) {
if (l == r) {
t[u] = {l, l};
return;
}
int mid = l + r >> 1;
Build(u << 1, l, mid), Build(u << 1 | 1, mid + 1, r);
t[u] = t[u << 1] + t[u << 1 | 1];
}
T Query(int u, int l, int r, int L, int R) {
if (L <= l && r <= R) return t[u];
int mid = l + r >> 1;
if (R <= mid) return Query(u << 1, l, mid, L, R);
else if (L > mid) return Query(u << 1 | 1, mid + 1, r, L, R);
else return Query(u << 1, l, mid, L, R) + Query(u << 1 | 1, mid + 1, r, L, R);
}
int main() {
freopen("wanted.in", "r", stdin);
freopen("wanted.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> q;
for (int i = 1, u, v, w; i < n; i++) {
cin >> u >> v >> w;
g[u].push_back({w, v});
g[v].push_back({w, u});
}
Dfs(1, 0);
for (int j = 1; j < kL; j++) {
for (int i = 1; i + (1 << j) - 1 <= tot; i++) {
f[i][j] = Min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
Build(1, 1, n);
for (int l1, r1, l2, r2; q; q--) {
cin >> l1 >> r1 >> l2 >> r2;
A = Query(1, 1, n, l1, r1), B = Query(1, 1, n, l2, r2);
Pii t[4] = {{A.l, B.l}, {A.l, B.r}, {A.r, B.l}, {A.r, B.r}};
Pii x = *min_element(t, t + 4, [](Pii i, Pii j) { return Dis(i.first, i.second) > Dis(j.first, j.second); });
cout << Dis(x.first, x.second) << '\n';
}
return 0;
}
反思:
一开始思路想假了,过了大样例以为是对的,没想到随便都可以 hack 掉(🤡
记牢这个性质,很有用。给个例题吧:https://www.luogu.com.cn/problem/P6845。

浙公网安备 33010602011771号