2025/11/3 ~ 2025/11/9 做题笔记

2025/11/3

下午在开批斗大会。因为 CSP-S 打炸了,所以复习了一下最小生成树。然而看了生成树剩下的几题,除了一题是 brovuka,其他的题都直接秒掉了,真不明白为什么 CSP-ST2 不会。

2025/11/4

A. G75 兰海高速

这道题目其实并不是很难,但是花了 1.5h,主要的问题还是代码写少了导致要优化和调很久才能写对。感觉一个很大的问题是我的思路总是非常的复杂,感觉还是我太菜了

Code
#include <chrono>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>

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

const int kN = 5e5 + 1;

vector<int> e[kN];
unordered_set<int> c[kN];
int T, n, fl, ans = 1, cnt;
unordered_map<int, int> a[kN];
unordered_map<int, pii> ed[kN];

void Init(int x, int fa) {
  if (x != 1) {
    bool f = 0;
    for (auto [i, _] : a[x]) {
      if (a[fa][i] && f)
        ans = 0;
      if (a[fa][i]) {
        f = 1;
        if (ed[fa][i].first)
          ed[fa][i].second = x;
        else
          ed[fa][i].first = x;
        ed[x][i].first = fa;
      }
    }
  }
  for (int i : e[x])
    if (i != fa)
      Init(i, x);
}
void Dfs(int x, int fa, int t) {
  cnt++;
  if (a[x][t] == 1)
    fl++;
  if (ed[x][t].first == fa && ed[x][t].second)
    Dfs(ed[x][t].second, x, t);
  else if (ed[x][t].first != fa)
    Dfs(ed[x][t].first, x, t);
}

int main() {
  auto start = chrono::high_resolution_clock::now();
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  for (int t = 1; T--; t++, ans = 1, cnt = fl = 0) {
    cin >> n;
    for (int i = 1; i <= n; i++) 
      a[i].clear(), e[i].clear(), c[i].clear(), ed[i].clear();
    for (int i = 1, x, y; i < n; i++)
      cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
    for (int i = 1, d; i <= n; i++) {
      cin >> d;
      for (int j = 1, x; j <= d; j++) {
        cin >> x;
        c[x].insert(i);
        if (++a[i][x] > 2)
          ans = 0;
      }
    }
    Init(1, -1);
    for (int i = 1; i <= n; i++, fl = cnt = 0) {
      if (c[i].empty() || !ans)
        continue;
      Dfs(*c[i].begin(), -1, i);
      if (fl != 2 || cnt != (int)c[i].size())
        ans = 0;
    }
    cout << (ans ? "Yes\n" : "No\n");
  }
  auto end = chrono::high_resolution_clock::now();
  chrono::duration<double> time = end - start;
  cerr << time.count();
  return 0;
}

B. Phigros

很容易发现答案是最终是 有 0 的列数 和 有 1 的行数 取 max,然后直接 dp。假设 \(n \leq m\),设 \(f_{i, j}\) 表示前 \(i\) 列有 \(j\) 列有 0 的方案书,然后发现不好处理有多少行有 1,然后就没什么思路了。其实静下心想想应该能发现如果存在一列没有 0,那么这一列全为 1,因此所有行都被占据过,而如果所有列都有 0,答案又不可能为 \(n\)。因此统计答案是好统计的,即 \(g_j = max(n, j) * f_{m, j}\)。然后的优化是套路的:对于 \(j \geq m\) 的时候答案的增加量为 \(f_j\),所以可以将从 \(m\) 优化到 \(n\)

Code
#include <iostream>
#include <vector>

using namespace std;

const int kN = 1e6 + 1, kM = 1e9 + 7;

char ch;
vector<int> f[kN], g[kN];
int n, m, ans, c[kN], d[kN], p[kN];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  p[0] = 1;
  for (int i = 1; i < kN; i++)
    p[i] = 2ll * p[i - 1] % kM;
  cin >> n >> m;
  if (n <= m) {
    for (int i = 1; i <= n; i++)
      for (int j = 1; j <= m; j++)
        cin >> ch, c[j] |= ch == '0', d[j] += ch == '?';
  } else {
    swap(n, m);
    for (int i = 1; i <= m; i++)
      for (int j = 1; j <= n; j++)
        cin >> ch, c[i] |= ch == '1', d[i] += ch == '?';
  }
  f[0].resize(n + 1), f[0][0] = 1;
  for (int i = 1; i <= m; i++) {
    c[i] ^= 1, f[i].resize(n + 1);  // 有 0 的时候是 0,没有 0 时是 1
    ans = (ans * c[i] + 1ll * (ans + f[i - 1][n]) * (p[d[i]] - c[i]) % kM) % kM, f[i][n] = 1ll * f[i - 1][n] * (p[d[i]] - c[i]) % kM, f[i][0] = f[i - 1][0] * c[i];
    for (int j = 1; j <= n; j++)
      f[i][j] = (f[i][j] + f[i - 1][j] * c[i] + 1ll * f[i - 1][j - 1] * (p[d[i]] - c[i]) % kM) % kM;
  }
  for (int i = 0; i <= n; i++)
    ans = (ans + 1ll * f[m][i] * n % kM) % kM;
  cout << ans << '\n';
  return 0;
}

2025/11/5 做题笔记

下午去爬了山,中午和晚上一直在写 #9127. Optimal Train Operation

2025/11/6 做题笔记

#9127. Optimal Train Operation

之前遗留下来的斜率优化,因为题面是英文一直没看

在每一个点修建地铁站的代价是 \(a_i\),每两个地铁站之间的代价是 \((j - i + 1)\max\limits_ {i \le k \le j}c_i\),第 0 个地方和第 n + 1 个位置已经修了地铁站。求最小代价

很容易写出状态转移方程 \(f_i = \min\limits_{1 \le j \le i}(f_{j - 1} + (i - j + 1)\max\limits_{j \le k \le i}c_i + a_i)\),之前做过类似的题,但是之前使用 CDQ 做的,现在尝试使用李超树和单调栈写,但是写了十万年。之前以为不用可持久化,但是实际上是要写可持久化的,李超树还写错了,调了好久

Code
#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

const int kN = 5e5 + 2, kV = 1e9;
const ll kI = 1e18;

int n, k, tp, c[kN], a[kN], rt[kN], st[kN];
vector<int> crt;
ll f[kN];
struct Line {
  ll k, b = kI;

  ll Calc(int x) { return k * x + b; }
};
struct Tr {
  Line li;
  int l, r;
} tr[kN * 50];

bool Cmp(Line a, Line b, int x) {
  return a.Calc(x) < b.Calc(x);
}
void Insert(int& x, int y, Line v, int l = 1, int r = kV) {
  if (y)
    tr[x = ++k] = tr[y];
  !x && (x = ++k);
  int m = (l + r) / 2;
  Cmp(v, tr[x].li, m) && (swap(tr[x].li, v), 0);
  if (Cmp(v, tr[x].li, l))
    Insert(tr[x].l, tr[y].l, v, l, m);
  else if (Cmp(v, tr[x].li, r))
    Insert(tr[x].r, tr[y].r, v, m + 1, r);
}
void Merge(int& x, int y, int l = 1, int r = kV) {
  if (!x || !y)
    return x = x + y, void();
  int m = (l + r) / 2;
  Insert(x, 0, tr[y].li, l, r);
  Merge(tr[x].l, tr[y].l, l, m), Merge(tr[x].r, tr[y].r, m + 1, r);
}
ll Query(int x, int p, int l = 1, int r = kV) {
  if (!x)
    return kI;
  if (l == r)
    return tr[x].li.Calc(p);
  int m = (l + r) / 2;
  ll res = tr[x].li.Calc(p);
  if (p <= m)
    return min(res, Query(tr[x].l, p, l, m));
  return min(res, Query(tr[x].r, p, m + 1, r));
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> c[i];
  for (int i = 1; i < n; i++)
    cin >> a[i];
  crt.push_back(0), f[1] = a[1] + c[1];
  Insert(rt[1], 0, {0, 0}), st[++tp] = 1;
  crt.push_back(++k), Insert(crt.back(), 0, {c[1], 0});
  for (int i = 2; i <= n; i++) {
    for (; tp && c[i] >= c[st[tp]]; tp--, crt.pop_back())
      Merge(rt[i], rt[st[tp]]);
    if (!tp)
      f[i] = Query(rt[i], c[i]) + 1ll * i * c[i] + a[i];
    else if (!rt[i])
      f[i] = Query(crt.back(), i) + a[i];
    else
      f[i] = min(Query(rt[i], c[i]) + 1ll * i * c[i], Query(crt.back(), i)) + a[i];
    if (i == 6)
      cerr << Query(rt[i], c[i]) << '\n';
    f[i] = min(f[i], f[i - 1] + c[i] + a[i]);
    Insert(rt[i], 0, {-i + 1, f[i - 1]}), st[++tp] = i;
    int t = crt.back();
    crt.push_back(++k), Insert(crt.back(), t, {c[i], Query(rt[i], c[i])});
  }
  cout << f[n];
  return 0;
}

A. チェリーポップ

期望得分:100pts

实际得分:100pts

时间分配:3h

一开始猜了个结论,但是显然是错的,后来有想了很久,决定先打个暴力再找规律。暴力还调了比较久的时间。找到规律写了双指针,不敢确认还写了个拍子,花费了很久的时间。

我觉得问题主要在于我没有发现这个东西是有单调性的,这题的单调性在于越长的区间比短的区间一定更加可能合法,因为包含了一段短的合法区间后,再加入额外一段长的合法区间时,可以理解为分别重排后也可以合法。考虑极端数据重排之后会使得交界处相同,但是举了一点例子后发现并不可能。想不到的时候真应该直接猜有单调性,缩小枚举范围,CSP-S T2 也应该尝试这么做的,实在举不出来反例就可以假设他有了

Code
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>

using namespace std;

const int kN = 2e6 + 1;

int T, n, l, r, ans = kN, a[kN][3];
map<char, int> mp;
string s;

bool Check(int l, int r) {
  int p = 0, c[3];
  for (int o : {0, 1, 2}) {
    c[o] = a[r][o] - a[l - 1][o];
    if (c[o] > c[p])
      p = o;
  }
  if (c[p] > (r - l + 2) / 2)
    return 0;
  if (c[p] == (r - l + 2) / 2) {
    if ((r - l + 1) & 1)
      return mp[s[l - 1]] != p && mp[s[r + 1]] != p;
    int _p = -1;
    for (int o : {0, 1, 2})
      if (o != p && (_p == -1 || c[_p] < c[o]))
        _p = o;
    return mp[s[l - 1]] != mp[s[r + 1]] || (mp[s[l - 1]] != p && (mp[s[l - 1]] != _p || c[_p] != (r - l + 1) / 2));
  }
  return 1;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  mp['C'] = 0, mp['W'] = 1, mp['P'] = 2, mp[' '] = 114514;
  for (cin >> T; T--; ans = kN) {
    cin >> n >> s, s = ' ' + s + ' ';
    for (int i = 1; i <= n; i++)
      for (int o : {0, 1, 2})
        a[i][o] = a[i - 1][o] + (mp[s[i]] == o);
    for (l = 2; l <= n && s[l] != s[l - 1]; l++);
    for (r = n - 1; r >= 1 && s[r] != s[r + 1]; r--);
    if (l > n) {
      cout << "0\n";
      continue;
    }
    if (l > r) {
      int j;
      for (j = l + 1; j <= n && !Check(l, j); j++);
      if (j <= n)
        ans = min(ans, j - l + 1);
      for (j = r - 1; j >= 1 && !Check(j, r); j--);
      if (j >= 1)
        ans = min(ans, r - j + 1);
      swap(l, r);
    }
    int _l = 1, _r = r;
    for (; _l <= l && _r <= n; _l++) {
      for (; _r <= n && !Check(_l, _r); _r++);
     if (_r <= n)
        ans = min(ans, _r - _l + 1);
    }
    if (ans == kN)
      cout << "-1\n";
    else
      cout << ans << '\n';
  }
  return 0;
}

B. ラビットホール

期望得分:24pts

实际得分:24pts

时间分配:1h

想到了叶子实际上没什么用,但是剩下的时间不够了想打 T3 暴力,所以没有继续想了。调了也挺久的。。。

要能想到双指针,因为大的区间合法小的就一定合法。考虑加点和删点的过程,唯一的难点是每次加点和删点的时候枚举产生变化的点(从叶子到非叶子或者从非叶子到叶子)所连的边是复杂度正确的,感觉上是对的,但是证明可能比较复杂

Code
#include <iostream>
#include <vector>

using namespace std;

const int kN = 1e6 + 1;

int n, m, q, mx, deg[kN], sec[kN], sta[kN], cnt[kN], ans[kN];
vector<int> e[kN];

void Add(int x, int l, int r) {
  if (sta[x] || deg[x] < 2)
    return;
  sta[x] = 1;
  for (int i : e[x]) {
    if (i < l || i >= r || !sta[i])
      continue;
    cnt[sec[i]]--;
    mx = max(mx, ++sec[i]), cnt[sec[i]]++;
    sec[x]++;
  }
  mx = max(mx, sec[x]), cnt[sec[x]]++;
}
void Del(int x, int l, int r) {
  if (!sta[x] || deg[x] >= 2)
    return;
  sta[x] = 0, cnt[sec[x]]--, sec[x] = 0;
  for (int i : e[x]) {
    if (i < l || i > r || !sta[i])
      continue;
    cnt[sec[i]]--, cnt[--sec[i]]++;
  }
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> q;
  for (int i = 1, x, y; i <= m; i++)
    cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
  for (int l = 1, r = 0; l <= n; l++) {
    for (r += (mx <= 2); r <= n && mx <= 2; r += (mx <= 2)) {
      for (int i : e[r])
        if (l <= i && i <= r)
          deg[i]++, deg[r]++, Add(i, l, r);
      Add(r, l, r);
    }
    ans[l] = r;
    deg[l] = 0, Del(l, l, r);
    for (int i : e[l])
      if (l <= i && i <= r)
        deg[i]--, Del(i, l, r);
    for (; mx > 0 && cnt[mx] <= 0; mx--);
  }
  for (int l, r; q--;) {
    cin >> l >> r;
    cout << (r < ans[l] ? "Yes\n" : "No\n");
  }
  return 0;
}

2025/11/7

C. ポジティブ · パレード

会了但是不完全会。写了一天还没写出来,又 WA 又 MLE 根本调不出来

由于杀怪物是单向的,显然使用攻击最大的去杀怪物是最优的。考虑把怪物分成生命值大于最大攻击的和小于最大攻击的,容易发现第一种一定存在 \(Att_i < Def_i\) 。现在希望把攻击最大的怪物也杀掉,那么会发现他一定是存在一条击杀链,并且最后一个怪物的防御力应该小于不可被杀的怪物中攻击力的最大值。攻击的最大值是好维护的,不可被杀的怪物的攻击最大值直接用线段树维护也是简单的,唯一的问题在于如何求击杀链中最小的防御?容易发现总是存在 \(Att_i \geq Def_{i + 1}\),考虑所有的区间 \((def_i, Att_i]\),他们的并一定是一段连续的区间,那么想要求得最小的防御值也是简单的,等价于求最大的未被任意区间覆盖的位置。

总结:

  1. 能不动态开点尽量不动态开点,动态开点的时候要注意 Pushup 和 Pushdown 的时候会不会涉及空节点
  2. 注意一下线段树里面有没有把左儿子和右儿子写错,以及函数调用有没有搞错变量位置

2025/11/8

A. 宇宙(universe)

时间分配:2.5h

比赛开始的时候脑子不知道在想什么,去想固定 \(k\) 的时候怎么 \(n \log n\) check,反应过来的时候已经过了 30min,但是一点用都没有。然后立马想到坐标大于可能答案的地方没用,于是去写了双指针。犯了各种各样的唐诗错误终于挑出来了。

总结:还是代码能力太差了,细节处理不好

Code
#include <algorithm>
#include <iostream>

using namespace std;
using ll = long long;

const int kN = 1e6 + 2;

ll a[kN];
int c, n;

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> c >> n;
  for (int i = 1; i <= n; i++)
    cin >> a[i];
  sort(a + 1, a + n + 1), a[n + 1] = 1e18;
  ll s = 0, _s = 0, ans = 0;
  for (int i = 1, j = 2; i < n; i++) {
    _s += a[i] - 1;
    if (j <= i + 2) {
      s = 0, ans = a[i + 1] + _s;
    } else {
      ll t = s + ans - 1;
      ans += t / (j - i - 1), s = t % (j - i - 1);
    }
    for (j = max(i + 2, j); ans > a[j] && j <= n + 1; j++) {
      ll t = s + (j - i - 1) * (ans - a[j]);
      s = t % (j - i), ans = a[j] + t / (j - i);
      if (t < j - i)
        break;
    }
    cout << ans - 1 << ' ';
  }
  return 0;
}

B. 跳跃(jump)

时间分配:1h

虽然没想到倍增,但是想到了扫描线,但是实际上并不好做,因为要把前 \(k\) 个点的所有答案取 min,或者说对黑色连续段缩一下也能做。但是考场上对这种做法深信不疑,写了半天还写挂了,连 \(t = 0\) 的分都没拿完

显然是能跳到最远的白点就跳到最远的白点的,因此每个白点向最远的白点连一条边。这样会形成很多条链,如果查询的两点不在同一条链上怎么办?可以直接选取任意一个点倍增。(或者说选择左端点最远的在另一条链上的点,直接算长度,好像没有人尝试过,但是感觉是对的,也可能不对)。

Code
#include <algorithm>
#include <iostream>
#include <vector>

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

const int kN = 5e5 + 1, kM = 23;

string s;
pii ans[kN];
vector<int> st;
int c, n, q, k, t, cnt, a[kN], p[kN], mp[kN], f[kN][kM];

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> c >> n >> q >> k >> t >> s, s = ' ' + s;
  for (int i = 1, c = 0; i <= n; i++) {
    if (!cnt && s[i] == '0')
      continue;
    if (s[i] == '0') {
      c++;
    } else {
      for (int j = 1; j <= c % k; j++)
        cnt++, p[cnt] = p[cnt - 1];
      cnt++, a[mp[i] = cnt] = 1, p[cnt] = p[cnt - 1] + c / k;
      c = 0, st.push_back(cnt);
    }
  }
  for (int i = 2; i <= cnt; i++) {
    if (a[i] == 0)
      continue;
    f[i][0] = *lower_bound(st.begin(), st.end(), i - k);
    for (int k = 1; k < kM; k++)
      f[i][k] = f[f[i][k - 1]][k - 1];
  }
  for (int l, r, s = 0, ans = 1e9; q--; s = 0, ans = 1e9) {
    cin >> l >> r;
    (l > r) && (swap(l, r), 0);
    l = mp[l], r = mp[r];
    for (int i = kM - 1, x = r; i >= 0; i--) {
      if (f[x][i] > l)
        x = f[x][i], s += 1 << i;
      else
        ans = min(ans, s + (1 << i));
    }
    cout << p[r] - p[l] << ' ';
    if (t)
      cout << p[r] - p[l] + ans << '\n';
    else
      cout << '\n';
  }
  return 0;
}
posted @ 2025-11-06 21:45  sb-yyds  阅读(17)  评论(2)    收藏  举报