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

posted @ 2025-05-04 17:36  liruixiong0101  阅读(22)  评论(0)    收藏  举报