2025/11/24~2025/11/28 做题笔记

2025/11/24

Codeforces Round 1066 (Div. 1 + Div. 2)

A. Dungeon Equilibrium

比较简单,很明显要么全部清除要么削减到 \(a_i = i\),直接算即可

Code
#include <iostream>
#include <map>

using namespace std;

int T, n, ans;
map<int, int> mp;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n;
    ans = 0, mp.clear();
    for (int i = 1, x; i <= n; i++)
      cin >> x, mp[x]++;
    for (int i = 0; i <= n; i++)
      ans += mp[i] >= i ? (mp[i] - i) : mp[i];
    cout << ans << '\n';
  }
  return 0;
}

B. Expansion Plan 2

统一处理那么多格子的变化并不好作,发现只有一条路径是有效的,那么原问题等价于可以选择一个方向走或者斜着走,能否走到 \((x, y)\)。那么很明显 \(4、8\) 的顺序不重要。同时 \(4\) 表示可以选择一个方向走一步,\(8\) 表示两个方向都走一步。那么直接看 \(\max(x - b, 0) + \max(0, y - b)\) 是否比 \(a\) 大即可,\(a\) 表示 \(4\) 的出现数量,\(b\) 表示 \(8\) 的出现次数

Code
#include <iostream>
#include <map>

using namespace std;

int T, n, x, y, a, b;
string s;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> x >> y >> s, x = abs(x), y = abs(y);
    a = b = 0;
    for (auto i : s)
      a += i == '4', b += i == '8';
    x -= b, y -= b;
    cout << (max(x, 0) + max(y, 0) <= a ? "yes\n" : "no\n");
  }
  return 0;
}

C. Meximum Array 2

要注意到所有操作都只有统一的一个值 \(k\)。那么构造并不难

  1. \(i\) 只有 \(\min\) 限制。直接令 \(a_i = k\) 即可
  2. \(i\) 只有 \(mex\) 限制。直接从 \(0~k - 1\) 按顺序轮着放即可。如果有两个限制相交,合并起来在做
  3. \(i\) 两种限制都有。令 \(a_i = k + 1\)
Code
#include <iostream>

using namespace std;

const int kN = 101;

int T, n, k, q, a[kN], tag[kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> k >> q;
    fill(tag, tag + n + 1, 0);
    for (int i = 1, c, l, r; i <= q; i++) {
      cin >> c >> l >> r;
      for (; l <= r; l++)
        tag[l] |= c;
    }
    for (int i = 1, j = 0; i <= n; i++) {
      if (tag[i] == 1)
        a[i] = k;
      else if (tag[i] == 2)
        a[i] = j, j = (j + 1) % k;
      else
        a[i] = k + 1;
    }
    for (int i = 1; i <= n; i++)
      cout << a[i] << ' ';
    cout << '\n';
  }
  return 0;
}

D. Billion Players Game

感觉比 E 要难一点

首先需要想到选择一定是成对出现的,并且贡献非负,所以最多舍弃 \(1\) 个点不选

\(Solution1\)

对我来说更自然的想法。直接枚举这个不选择的点,发现前缀一定是向后,后缀一定是向前。一开始假设所有点都向前,往后扫的同时算出把这个点改成向后后的答案即可。因为是两个端点选最差的,那么同时维护在两个端点的取值

Code
#include <algorithm>
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 2e5 + 1;

int T, n, L, R, a[kN];
ll l, r, ans;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> L >> R;
    for (int i = 1; i <= n; i++)
      cin >> a[i];
    sort(a + 1, a + n + 1), l = r = 0;
    for (int i = 1; i <= n; i++)
      l += a[i] - L, r += a[i] - R;
    ans = min(l, r);
    for (int i = 1; i <= n; i++) {
      l += (L - a[i]) - (a[i] - L), r += (R - a[i]) - (a[i] - R);
      ans = max({ans, min(l - (L - a[i]), r - (R - a[i])), min(l, r)});
    }
    cout << ans << '\n';
  }
  return 0;
}

\(Solution2\)

巨佬 yq 的做法。显然对于在 \([l, r]\) 以外的点的选法已经固定了,考虑把这些点先算上,最终答案会形成一个一次函数。对于在 \([l, r]\) 内的点,先把 \(k\) 调成 0,再配对选即可

Code
#include <algorithm>
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 2e5 + 1;

int T, n, L, R, a[kN];
ll k, b;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> L >> R;
    for (int i = 1; i <= n; i++)
      cin >> a[i];
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; i++) {
      if (a[i] < L)
        k++, b -= a[i];
      else if (a[i] > R)
        k--, b += a[i];
    }
    int l, r;
    for (l = 1; l <= n && a[l] < L; l++);
    for (r = n; r >= 1 && a[r] > R; r--);
    if (k > 0) {
      for (; r >= l && k; r--)
        k--, b += a[r];
    } else {
      for (; l <= r && k; l++)
        k++, b -= a[l];
    }
    for (; l < r; l++, r--)
      b += a[r] - a[l];
    cout << min(k * L + b, k * R + b) << '\n';
    k = b = 0;
  }
  return 0;
}

E. Adjusting Drones

首先需要转化一下题意,这一步是平凡的。接下来同样有两个做法

\(Solution1\)

jjz 发现从后往前依次把能搞的搞完不回影响原答案。直接用线段树维护每个位置的值,只需要区间覆盖和线段树上二分操作即可

\(Solution2\)

有更加简单粗暴的做法。直接往前推,能走就走就行。最后对于每一段的答案取个 \(max\)

Code
#include <cassert>
#include <iostream>

using namespace std;

const int kN = 1e6 + 1;

int T, n, k, ans, a[kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T--; ans = 0) {
    cin >> n >> k;
    for (int i = 1, x; i <= n; i++)
      cin >> x, a[x]++;
    for (int i = 1, s = 0; i <= 2 * n; i++, s = 0) {
      for (; a[i] > k; i++, s++)
        a[i + 1] += a[i] - 1;
      ans = max(ans, s);
    }
    fill(a, a + 3 * n + 1, 0);
    cout << ans << '\n';
  }
  return 0;
}

2025/11/25

A. Noip

怎么还会忘记开 long long……其实做过比较多的这种题,差不多都是从最大或做小的情况开始慢慢调整到符合答案的情况。对于这题比较容易发现一条链的答案是最大的,最大为 \(\dfrac{n(n - 1)}{2}\)。那么从最后一个点开始算放到哪里就做完了

Code
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 1e6 + 1;

int T, n, fa[kN];
ll k;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> k;
    if (k > 1ll * n * (n - 1) / 2 || k < n - 1) {
      cout << "-1\n\n";
      continue;
    }
    k = 1ll * n * (n - 1) / 2 - k;
    for (int i = 1; i < n; i++)
      fa[i] = i + 1;
    for (int i = 1; i < n && k; i++) {
      if (k > n - i - 1)
        k -= n - i - 1, fa[i] = n;
      else
        fa[i] = n - (n - i - k - 1), k = 0;
    }
    for (int i = 1; i < n; i++)
      cout << i << ' ' << fa[i] << '\n';
    cout << '\n';
  }
  return 0;
}

B. nOip

比较亲民的博弈论。首先将左括号和当前局面小B先手设为 0,右括号和当前局面教练先手设为 1。不难发现每种局面一定只会选择对应的边,不然下一回合对手再跳回来会使得局面更差。那么发现对于一个全都是 1 的边的点,是小B先手必败的点。可以知道只要能不走到必败局面,小B就一定会胜利,一直左右括号一定是合法的。

那么最后的解法就是从每个小B先手必败的局面枚举上一个点,如果上一个局面为 1,那么可以加入队列。如果上一个局面为 0,判断一下是不是所有出边都是必败局面或不可以到达,如果是那么加入队列。注意到这样可能导致枚举的边很多,可以设 \(deg_x\) 表示 \(x\) 点出初始有 \(deg_x\)\(0\) 边可以走,每有一个必败局面就减一,直到减完就将这个点加入队列

一开始没清空数组调了 1h,清空数组是真应该写在最前面,最后写容易忘

Code
#include <iostream>
#include <queue>
#include <vector>

using namespace std;
using pii = pair<int, int>;

const int kN = 1e6 + 1;

int T, n, m, d[kN], s[kN][2];  // 0:小 B 1:教练
vector<pii> e[kN];
queue<pii> q;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> m;
    // cerr << T << ' ' << n << ' ' << m << '\n';
    for (int i = 1; i <= n; i++)
      s[i][0] = s[i][1] = d[i] = 0, e[i].clear();
    for (int i = 1, x, y, z; i <= m; i++) {
      cin >> x >> y >> z;
      e[x].push_back({y, z}), e[y].push_back({x, z});
    }
    for (int i = 1; i <= n; i++) {
      bool f = 1;
      for (auto [y, z] : e[i])
        f &= z, d[i] += !z;
      f && (q.push({i, 0}), 0);
    }
    for (; q.size(); q.pop()) {
      auto [t, o] = q.front();
      s[t][o] = 1;
      if (o == 0) {
        for (auto [y, z] : e[t])
          (z && !s[y][1]) && (q.push({y, 1}), s[y][1] = 1);
      } else {
        for (auto [y, z] : e[t]) {
          if (z)
            continue;
          if (--d[y] == 0)
            q.push({y, 0});
        }
      }
    }
    for (int i = 1; i <= n; i++)
      cout << (s[i][0] ? 0 : 1);
    cout << '\n';
  }
  return 0;
}

CF2061E Kevin and And

首先可以 \(n2^m\) 预处理每个数字 \(m\) 此操作后最小会变成啥。然后有一个小观察:它的差分数组是单调不增的,所以暴力选即可。

虽然这也太难发现了,但是 OI 不需要证明,先猜个凸性做题

#include <bitset>
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

const int kN = 1e5 + 1, kM = 11, kV = (1 << 30) - 1;

int T, n, m, k, b[kM], a[kN][kM];
vector<int> vec;
ll ans;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T--;) {
    cin >> n >> m >> k;
    fill(a[0], a[n + 1], kV), vec.clear(), ans = 0;
    for (int i = 1; i <= n; i++)
      cin >> a[i][0], ans += a[i][0];
    for (int i = 0; i < m; i++)
      cin >> b[i];
    for (int s = 1; s < 1 << m; s++) {
      int res = kV;
      for (int i = 0; i < m; i++)
        if (s & (1 << i))
          res &= b[i];
      for (int i = 1; i <= n; i++)
        a[i][__builtin_popcount(s)] = min(a[i][__builtin_popcount(s)], a[i][0] & res);
    }
    for (int i = 1; i <= n; i++)
      for (int j = 1; j <= m; j++)
        vec.push_back(a[i][j] - a[i][j - 1]);
    sort(vec.begin(), vec.end());
    for (int i = 0; i < k; i++)
      ans += vec[i];
    cout << ans << '\n';
  }
  return 0;
}

2025/11/26

CF2063E Triangle Tree

读题一定要多读几遍,看样例解释,清楚的理解题意。

首先有 \(|dis_{u, lca} - dis_{v, lca}| < x < dis_{u, lca} + dis_{v, lca}\),显然 \(dis\) 不太好做,转化成 \(dep\),于是有 \(|dep_u - dep_v| < x < dep_u + dep_v - 2dep_{lca}\),假设 \(dep_u < dep_v\),那么 \(f(u, v) = 2dep_u - 2dep_{lca} - 1\),显然可以三部分分开求

  1. \(2dep_u\)。会产生贡献当且仅当 \(dep_u \leq dep_v\),同时目标点不可为子树内的节点,那么统计一下后缀的个数,每次经过一个 \(dep_u\) 就将令 \(cnt_{dep_u}\) 减一,这样每次统计答案即为 \(2 * dep_u * (cnt_{dep_u} - 1 - (sz_u - 1))\)
  2. \(2dep_{lca}\)。考虑 dfs 的时候顺便求出有多少对答案的 \(lca\)\(x\)。即为任意两个子树内的点两两配对,做个前缀和轻松解决
  3. -1。即为统计有多少个有多少个合法对,考虑每一个点对应的不合法点对数量为所有父亲,答案即为 \(n - \sum (dep_i - 1)\)
Code
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using ll = long long;

const int kN = 3e5 + 2;

int T, n, sz[kN], dep[kN], cnt[kN];
vector<int> e[kN];
ll ans;

void Dfs(int x, int fa) {
  cnt[dep[x] = dep[fa] + 1]++, sz[x] = 1;
  for (int i : e[x]) {
    if (i == fa)
      continue;
    Dfs(i, x);
    ans -= 2ll * dep[x] * (sz[x] - 1) * sz[i];
    sz[x] += sz[i];
  }
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T--; ans = 0) {
    cin >> n;
    for (int i = 1, x, y; i < n; i++)
      cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
    Dfs(1, 0);
    for (int i = n - 1; i >= 1; i--)
      cnt[i] += cnt[i + 1];
    for (int i = 1; i <= n; i++)
      ans += 2ll * dep[i] * (cnt[dep[i]] - sz[i]), cnt[dep[i]]--;
    cout << ans - (1ll * n * (n - 1) / 2 - (accumulate(dep + 1, dep + n + 1, 0ll) - n)) << '\n';
    for (int i = 1; i <= n; i++)
      e[i].clear(), sz[i] = dep[i] = cnt[i] = 0;
  }
  return 0;
}

CF2063D Game With Triangles

转化一下题意说的是在任意一根线上选取两个点和另一根线上一点删掉, 并累加两个点之间的长度. 显然如果选两个点一定是选最远的两个点, 始终不劣. 那么可以预处理出来在第一根线上选 \(f(x)\) 次, 在第二根线上选 \(g(x)\) 次的最优值. 如何求 \(f(n) + g(k - n)\) 的最优值?发现这两个函数都是单调的, 然后猜这两个函数都是凸的. 然而他们确实是凸的, 因为能产生的贡献越来越少, 所以 \(f(n) + g(k - n)\) 也是凸的, 直接三分或者倍增即可

Code
#include <algorithm>
#include <chrono>
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 2e5 + 1;
const ll kI = 1e18;

int T, n, m, len, a[kN], b[kN];
ll f[kN], g[kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T--;) {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
      cin >> a[i];
    for (int i = 1; i <= m; i++)
      cin >> b[i];
    sort(a + 1, a + n + 1), sort(b + 1, b + m + 1);
    if (n < m) {
      for (int i = 1; i <= m; i++)
        swap(a[i], b[i]);
      swap(n, m);
    }
    for (int i = 1, l = 1, r = n; l < r; i++, l++, r--)
      f[i] = f[i - 1] + a[r] - a[l];
    for (int i = 1, l = 1, r = m; l < r; i++, l++, r--)
      g[i] = g[i - 1] + b[r] - b[l];
    if (n - m > m) {
      len = m;
    } else {
      int t = m - (n - m);
      len = n - m + 2 * (t / 3) + (t % 3 == 2);
    }
    cout << len << '\n';
    for (int k = 1; k <= len; k++) {
      auto Calc = [&k](int c) { return c > k || c < 0 || (2 * c + (k - c)) > n || (c + 2 * (k - c)) > m ? -kI : f[c] + g[k - c]; };
      int m = max(2 * k - ::m, 0);
      for (int i = 23; i >= 0; i--) {
        int l = m - (1 << i), r = m + (1 << i);
        if (Calc(l) >= Calc(r) && Calc(l) > Calc(m))
          m = l;
        else if (Calc(r) > Calc(m))
          m = r;
      }
      cout << Calc(m) << ' ';
    }
    if (len)
      cout << '\n';
    fill(a + 1, a + n + 1, 0), fill(b + 1, b + m + 1, 0), fill(f + 1, f + n + 1, 0), fill(g + 1, g + m + 1, 0);
  }
  return 0;
}
posted @ 2025-11-24 20:59  sb-yyds  阅读(16)  评论(0)    收藏  举报