2025/10/20~2025/10/26 做题笔记

2025/10/20

AT_arc181_d Prefix Bubble Sort

很显然的有每一次交换都会恰好减少一个逆序对,于是题目转化为每次会产生多少次交换。

那么考虑如何统计交换次数

  • 发现当前缀 max 变化时不会产生答案,但是这个折线非常困难维护,不考虑这种做法
  • 考虑每个数往后交换多少次是难的,那么考虑有多少个数会往前交换。可以发现当前面有数大于这个数时,这个数一定会产生一次交换,那么直接统计有多少个数前面有大于它的数即可。由于题目限制了 \(A_i\) 单调不减,直接线段树简单维护即可

没有想到可以换一种贡献方式,老是在一棵树上吊死。

Code

#include <iostream>

using namespace std;
using ll = long long;

const int kN = 2e5 + 1;

int n, m, a[kN];
ll ans;
struct BIT {
  int tr[kN];

  void Update(int x, int k) {
    for (; x < kN; x += x & -x)
      tr[x] += k;
  }

  int Query(int x) {
    int res = 0;
    for (; x; x -= x & -x)
      res += tr[x];
    return res;
  }
} Bit;
struct Point {
  int mn, cnt, tag;
} tr[kN << 2];

void Func(int x, int k) {
  tr[x].mn += k, tr[x].tag += k;
}
void Pushdown(int x) {
  Func(x * 2, tr[x].tag), Func(x * 2 + 1, tr[x].tag), tr[x].tag = 0;
}
void Find(int x, int l, int r) {
  if (tr[x].mn > 0)
    return;
  if (l == r)
    return tr[x].mn = tr[x].tag = 1e9, tr[x].cnt = 0, void();
  Pushdown(x);
  int m = (l + r) / 2;
  Find(x * 2, l, m), Find(x * 2 + 1, m + 1, r);
  tr[x].mn = min(tr[x * 2].mn, tr[x * 2 + 1].mn), tr[x].cnt = tr[x * 2].cnt + tr[x * 2 + 1].cnt;
}
void Update(int nl, int nr, int x = 1, int l = 1, int r = n) {
  if (nl <= l && r <= nr)
    return Func(x, -1), Find(x, l, r);
  Pushdown(x);
  int m = (l + r) / 2;
  if (nl <= m)
    Update(nl, nr, x * 2, l, m);
  if (nr > m)
    Update(nl, nr, x * 2 + 1, m + 1, r);
  tr[x].mn = min(tr[x * 2].mn, tr[x * 2 + 1].mn), tr[x].cnt = tr[x * 2].cnt + tr[x * 2 + 1].cnt;
}
void Build(int x = 1, int l = 1, int r = n) {
  if (l == r)
    return tr[x].cnt = a[l] > 0, tr[x].mn = a[l] ? a[l] : 1e9, void();
  int m = (l + r) / 2;
  Build(x * 2, l, m), Build(x * 2 + 1, m + 1, r);
  tr[x].mn = min(tr[x * 2].mn, tr[x * 2 + 1].mn), tr[x].cnt = tr[x * 2].cnt + tr[x * 2 + 1].cnt;
}
int Query(int nl, int nr, int x = 1, int l = 1, int r = n) {
  if (nl <= l && r <= nr)
    return tr[x].cnt;
  Pushdown(x);
  int m = (l + r) / 2, res = 0;
  if (nl <= m)
    res = Query(nl, nr, x * 2, l, m);
  if (nr > m)
    res += Query(nl, nr, x * 2 + 1, m + 1, r);
  return res;
}

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, x; i <= n; i++) {
    cin >> x;
    a[i] = Bit.Query(n - x + 1), Bit.Update(n - x + 1, 1), ans += a[i];
  }
  Build();
  cin >> m;
  for (int i = 1, x; i <= m; i++) {
    cin >> x;
    ans -= Query(1, x), Update(1, x);
    cout << ans << '\n';
  }
  return 0;
}

2025/10/21

A. makise

非常容易发现如果两个数加起来是奇数,则会有 1 的损耗。那么可以知道先手的策略一定是选取两个奇数,后手的策略一定是选取一奇一偶。以一轮先手后手操作为组,每轮消耗 3 个奇数,那么损耗就是 \(\lfloor \dfrac{cnt}{3} \rfloor + [cnt \mod 3 == 1]\),其中 \(cnt\) 表示奇数的个数

Code
#include <iostream>

using namespace std;
using ll = long long;

int n, odd;
ll s;

int main() {
#ifndef ONLINE_JUDGE
  freopen("makise.in", "r", stdin);
  freopen("makise.out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1, x; i <= n; i++) {
    cin >> x, odd += x & 1, s += x;
    if (i == 1)
      cout << s << ' ';
    else
      cout << s - (odd / 3 + (odd % 3 == 1)) << ' ';
  }
  return 0;
}

B. shop

可以发现最后的答案一定是只选择一个 \(b_i\),那么对于所有的 \(a_j \ge b_i\) 都要选择。判断一下 \(a_i\) 有没有被选择和选择的 \(a\) 的个数是否大于 \(m\) 即可。

双指针没有限制 \(j \le n\),给我糖丸了

Code
#include <algorithm>
#include <iostream>
#include <queue>

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

const int kN = 3e5 + 1;

priority_queue<int, vector<int>, greater<int>> qe;
int T, n, m, od[kN];
pii a[kN];
ll ans, s;

int main() {
#ifndef ONLINE_JUDGE
  freopen("shop.in", "r", stdin);
  freopen("shop.out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> m >> n;
    for (int i = 1; i <= n; i++) {
      cin >> a[i].first >> a[i].second;
      od[i] = i;
    }
    sort(a + 1, a + n + 1, greater<pii>()), sort(od + 1, od + n + 1, [](int x, int y) { return a[x].second > a[y].second; });
    for (int i = 1, j = 1; i <= n; i++) {
      for (; j <= n && a[j].first >= a[od[i]].second; j++) {
        if ((int)qe.size() < m)
          qe.push(a[j].first), s += a[j].first;
        else if (a[j].first > qe.top())
          s += qe.top() - a[j].first, qe.pop(), qe.push(a[j].first);
      }
      if (m < j) {
        ans = max(ans, s);
        continue;
      }
      if (a[od[i]].first >= a[od[i]].second)
        ans = max(ans, s + 1ll * (m - j + 1) * a[od[i]].second);
      else
        ans = max(ans, s + a[od[i]].first + 1ll * (m - j) * a[od[i]].second);
    }
    cout << ans << '\n';
    ans = s = 0;
    for (int i = 1; i <= n; i++)
      a[i].first = a[i].second = 0;
    priority_queue<int, vector<int>, greater<int>>().swap(qe);
  }
  return 0;
}

C. divide

注意到我这次没有打错名字()

才做了一道严格加强版 CF1175G Yet Another Partiton Problem 这道题还要上斜率优化。

除了普通的单调栈做法,还可以使用 CDQ 分治。具体来说考虑左边转移到右边,令 \(mn_i\) 表示 \([i, mid]/[mid + 1, i]\) 的 后缀/前缀 \(a_i\) 的最小值,分两种情况考虑 \(mn_l > mn_r\)\(mn_l < mn_r\) 即可,由于 \(mn\) 在两边是单调的,所以对于一个区间做一次 dp 可以做到线性,总体时间复杂度 \(\mathcal {O}(n \log n)\)

现在看起来好装逼啊,完全没必要用 CDQ 分治。

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

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

const int kN = 1e5 + 1;

int n, tp, a[kN], b[kN], c[kN], d[kN], st[kN];
ll f[kN];

void CDQ(int l, int r) {
  if (l == r)
    return f[l] = max(f[l], f[l - 1] + b[l]), void();
  int m = (l + r) / 2;
  CDQ(l, m);
  c[m] = a[m], c[m + 1] = a[m + 1], d[m] = b[m], d[m + 1] = b[m + 1];
  for (int i = m - 1; i >= l; i--) {
    c[i] = min(c[i + 1], a[i]);
    if (a[i] < c[i + 1])
      d[i] = b[i];
    else
      d[i] = d[i + 1];
  }
  for (int i = m + 2; i <= r; i++) {
    c[i] = min(c[i - 1], a[i]);
    if (a[i] < c[i - 1])
      d[i] = b[i];
    else
      d[i] = d[i - 1];
  }
  ll s = -1e18;
  for (int i = m + 1, j = m + 1; i <= r; i++) {
    for (; j >= l && c[j] >= c[i]; j--)
      s = max(s, f[j - 1]);
    f[i] = max(f[i], s + d[i]);
  }
  s = -1e18;
  for (int i = r, j = l; i >= m + 1; i--) {
    for (; j <= m + 1 && c[j] <= c[i]; j++)
      s = max(s, f[j - 1] + d[j]);
    f[i] = max(f[i], s);
  }
  CDQ(m + 1, r);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("divide.in", "r", stdin);
  freopen("divide.out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> a[i];
  for (int i = 1; i <= n; i++)
    cin >> b[i];
  fill(f + 1, f + kN, -1e18);
  CDQ(1, n);
  cout << f[n];
  return 0;
}

D. Hot-Hotels 加强版

这道题和 Hot-Hotels 完全一样,在 dongxi 题单之前见到过。但是之前没有补,考场上手推了一下。

\(lca(x, y, z)\) 统计答案是最方便的,那么符合条件的图长这样:

并且 \(x、y、z\)\(lca(x, y)\) 的距离都一样,设为 \(d\)。这个 \(d\) 不是很重要,我们只需要知道合法的 \(x、y\) 点对数量和符合条件的 \(z\) 数量即可。因此可设计状态:\(f_{i, j}\) 表示在 \(i\) 子树中,距离 \(i\)\(j\) 的点数量、\(g_{i, j}\) 表示 \(i\) 子树 \(lca(x, y)\) 中距离 \(i\) 距离为 \(d - j\) 的点对数量,或者说和一个与 \(i\) 距离为 \(j\) 的点能构成答案的 \(x、y\) 点对数量。

理解了状态设计,答案统计和 \(dp\) 转移是非常简单的(之前就是没有能理解状态设计才不会)

这个东西非常明显可以使用长链剖分转移,然而我长剖题就没写过几道,之前以一种诡异的方法实现的长剖,导致我这次对于 \(g_{i, j}\) 的答案继承上出了很大的问题,赛后才改出来。

Code
#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

const int kN = 1e5 + 1;

int n, tp[kN], s[kN], hs[kN], dep[kN];
ll ans, *f[kN], *g[kN], _[kN * 8], *p = _ + 1;
vector<int> e[kN];

void Dfs(int x, int fa) {
  s[x] = 1;
  for (int i : e[x]) {
    if (i == fa)
      continue;
    Dfs(i, x);
    if (s[i] + 1 > s[x])
      s[x] = s[i] + 1, hs[x] = i;
  }
}
void Dp(int x, int fa, bool t) {
  if (t)
    f[x] = f[fa] + 1, g[x] = g[fa] - 1;
  else
    f[x] = p, g[x] = p + 2 * (s[x] + 1), p += 4 * (s[x] + 1);
  if (hs[x])
    Dp(hs[x], x, 1);
  f[x][0] = 1, ans += g[x][0];
  for (int i : e[x]) {
    if (i == fa || i == hs[x])
      continue;
    Dp(i, x, 0);
    for (int j = 0; j < s[i]; j++)
      ans += g[x][j + 1] * f[i][j] + f[x][j - 1] * g[i][j];
    for (int j = 0; j < s[i]; j++) {
      g[x][j] += g[i][j + 1], g[x][j + 1] += f[x][j + 1] * f[i][j];
      f[x][j + 1] += f[i][j];
    }
  }
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("tree.in", "r", stdin);
  freopen("tree.out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(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), Dp(1, 0, 0);
  cout << ans;
  return 0;
}

2025/10/22 做题笔记

P6773 [NOI2020] 命运

我感觉这个 dp 应该比较的典,发现每个点子树内未满足的限制中只有最深的点有效,那么设计状态 \(f_{i, j}\) 表示子树 \(i\) 中,限制最深的点深度为 \(j\),设 \(b_i\) 表示每个点最深的限制,设 \(g_{i, j}\) 表示子树 \(i\)\(f_i\) 的前缀和。

设当前子树为 \(i\),儿子为 \(x\),考虑 dp 转移。

  1. \(x\) 的父亲边染色,那么对于儿子内的所有限制都满足

\[f_{i, j} = f_{i, j} \cdot g_{x, dep_x} \]

  1. \(x\) 的父亲边不染色,那么考虑是我这个点的限制更深还是子树内的限制更深

\[f_{i, j} = f_{i, j} \cdot g_{x, j - 1} + g_{i, j} \cdot f_{x, j - 1} + f_{i, j} \cdot f_{x, j} \]

综上有 \(f_{i, j} = f_{i, j} \cdot g_{x, dep_x} + f_{i, j} \cdot g_{x, j - 1} + g_{i, j} \cdot f_{x, j - 1} + f_{i, j} \cdot f_{x, j}\),进行小处理 \(f_{i, j} = f_{i, j}(g_{x, dep_x} + g_{x, j - 1} + f_{x, j}) + f_{x, j} \cdot g_{i, j - 1}\)

发现是 \(f_{i, j} \times a + f_{x, j} \times b\),考虑进行线段树合并优化。考虑线段树维护一段区间内的 \(f_{i}\) 的值,那么在线段树合并的时候顺便维护一下两个序列的前缀和,再 \(rt_x, rt_y\) 其中一个为空的时候打上乘法 tag 即可。tips: 要先递归合并右子树再合并左子树,因为合并左子树的时候会覆盖左边子树的答案,导致前缀和维护出错。

Code
#include <iostream>
#include <vector>

using namespace std;

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

int n, m, cnt, b[kN], rt[kN], dep[kN];
vector<int> e[kN];
struct Seg {
  int l, r, tag, sum;
} tr[kN * 20];

void Func(int x, int k) {
  tr[x].sum = 1ll * tr[x].sum * k % kM, tr[x].tag = 1ll * tr[x].tag * k % kM;
}
void Pushdown(int x) {
  Func(tr[x].l, tr[x].tag), Func(tr[x].r, tr[x].tag), tr[x].tag = 1;
}
void Update(int& x, int l, int r, int p) {
  x = ++cnt;
  tr[x].sum++, tr[x].tag = 1;
  if (l == r)
    return;
  Pushdown(x);
  int m = (l + r) / 2;
  if (p <= m)
    return Update(tr[x].l, l, m, p);
  Update(tr[x].r, m + 1, r, p);
}
void Merge(int& x, int y, int l, int r, int sumx, int sumy) {
  if (!x && !y)
    return;
  Pushdown(x), Pushdown(y);
  if (!x || !y) {
    !x && (swap(x, y), swap(sumx, sumy), 0);
    return Func(x, sumy);
  }
  if (l == r)
    return tr[x].sum = (1ll * tr[x].sum * sumy % kM + 1ll * tr[x].sum * tr[y].sum % kM + 1ll * tr[y].sum * sumx % kM) % kM, void();
  int m = (l + r) / 2;
  Merge(tr[x].r, tr[y].r, m + 1, r, (sumx + tr[tr[x].l].sum) % kM, (sumy + tr[tr[y].l].sum) % kM);
  Merge(tr[x].l, tr[y].l, l, m, sumx, sumy);
  tr[x].sum = (tr[tr[x].l].sum + tr[tr[x].r].sum) % kM;
}
int Query(int x, int l, int r, int nl, int nr) {
  if (nl <= l && r <= nr)
    return tr[x].sum;
  Pushdown(x);
  int m = (l + r) / 2, res = 0;
  if (nl <= m)
    res = Query(tr[x].l, l, m, nl, nr);
  if (nr > m)
    res = (res + Query(tr[x].r, m + 1, r, nl, nr)) % kM;
  return res;
}

void Init(int x, int fa) {
  dep[x] = dep[fa] + 1;
  for (int i : e[x])
    if (i != fa)
      Init(i, x);
}
void Dfs(int x, int fa) {
  Update(rt[x], 0, n, b[x]);
  for (int i : e[x]) {
    if (i == fa)
      continue;
    Dfs(i, x);
    Merge(rt[x], rt[i], 0, n, 0, Query(rt[i], 0, n, 0, dep[x]));
  }
}

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, x, y; i < n; i++)
    cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
  Init(1, 0);
  cin >> m;
  for (int i = 1, x, y; i <= m; i++) {
    cin >> x >> y;
    if (dep[y] > dep[x])
      swap(x, y);
    b[x] = max(b[x], dep[y]);
  }
  Dfs(1, 0);
  cout << Query(rt[1], 0, n, 0, 0);
  return 0;
}

P5298 [PKUWC2018] Minimax

很明显的设 \(f_{i, j}\) 表示 \(i\) 子树内,权值为 \(j\) 的概率,额外设 \(g_{i, j}\)\(h_{i, j}\) 分别表示后缀和和前缀和。

那么有转移式

\[f_{i, j} = (1 - p_i)(f_{s1, i} \cdot g_{s2, i + 1} + f_{s_2, i} \cdot g_{s_i, i + 1}) + p_i(f_{s_1, i} \cdot h_{s2, i - 1} + f_{s2, i} \cdot h_{s_1, i - 1}) + f_{s1, i} \cdot f_{s2, i} \]

化简得

\[f_{i, j} = f_{s1, i}(p_i \cdot h_{s2, i - 1} + (1 - p_i)g_{s_2, i + 1}) + f_{s2, i}(p_i \cdot h_{s_1, i - 1} + (1 - p_i)g_{s_1, i + 1}) + f_{s1, i} \cdot f_{s2, i} \]

和上一题一样的线段树合并技巧,不过这个需要多维护一个后缀

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

using namespace std;

const int kN = 3e5 + 1, kM = 998244353, kInv = 796898467;

int n, ans, cnt, p[kN], c[kN], rt[kN], s[kN][2];
struct Tr {
  int l, r, sum, tag;
} tr[kN * 20];

int Pow(int x, int y) {
  int res = 1;
  for (; y; y >>= 1, x = 1ll * x * x % kM)
    (y & 1) && (res = 1ll * res * x % kM);
  return res;
}
void Func(int x, int k) {
  tr[x].sum = 1ll * tr[x].sum * k % kM, tr[x].tag = 1ll * tr[x].tag * k % kM;
}
void Pushdown(int x) {
  Func(tr[x].l, tr[x].tag), Func(tr[x].r, tr[x].tag), tr[x].tag = 1;
}
void Set(int& x, int p, int l = 1, int r = n) {
  tr[x = ++cnt].sum = 1, tr[x].tag = 1;
  if (l == r)
    return;
  int m = (l + r) / 2;
  if (p <= m)
    return Set(tr[x].l, p, l, m);
  Set(tr[x].r, p, m + 1, r);
}
void Merge(int& x, int y, int p, int gx = 0, int gy = 0, int hx = 0, int hy = 0, int l = 1, int r = n) {  // g 表示后缀,h 表示后缀
  gx %= kM, gy %= kM, hx %= kM, hy %= kM;
  if (!x && !y)
    return;
  Pushdown(x), Pushdown(y);
  if (!x || !y) {
    !x && (swap(x, y), swap(gx, gy), swap(hx, hy), 0);
    return Func(x, 1ll * p * hy % kM + 1ll * (1 - p + kM) % kM * gy % kM);
  }
  int m = (l + r) / 2, _hx = tr[tr[x].l].sum, _hy = tr[tr[y].l].sum;
  Merge(tr[x].l, tr[y].l, p, gx + tr[tr[x].r].sum, gy + tr[tr[y].r].sum, hx, hy, l, m);
  Merge(tr[x].r, tr[y].r, p, gx, gy, hx + _hx, hy + _hy, m + 1, r);
  tr[x].sum = (tr[tr[x].l].sum + tr[tr[x].r].sum) % kM;
}
int Query(int& x, int p, int l = 1, int r = n) {
  if (!x)
    return 0;
  if (l == r)
    return tr[x].sum;
  Pushdown(x);
  int m = (l + r) / 2;
  return (p <= m) ? Query(tr[x].l, p, l, m) : Query(tr[x].r, p, m + 1, r);
}
void Dfs(int x) {
  if (!s[x][0])
    Set(rt[x], p[x]);
  if (s[x][0])
    Dfs(s[x][0]), rt[x] = rt[s[x][0]];
  if (s[x][1])
    Dfs(s[x][1]), Merge(rt[x], rt[s[x][1]], 1ll * c[p[x]] * 796898467 % kM);
  // cout << x << ":\n";
  // for (int i = 1; i <= n; i++)
  // cout << i << ' ' << Query(rt[x], i) << '\n';
}

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, x; i <= n; i++)
    cin >> x, s[x][1] = s[x][0], s[x][0] = i;
  for (int i = 1; i <= n; i++)
    cin >> p[i], c[i] = p[i];
  sort(c + 1, c + n + 1);
  for (int i = 1; i <= n; i++)
    p[i] = lower_bound(c + 1, c + n + 1, p[i]) - c;
  Dfs(1);
  for (int i = 1, res = 0; i <= n; i++) {
    int t = Query(rt[1], i);
    if (!t)
      continue;
    res++;
    ans = (ans + 1ll * res * c[i] % kM * t % kM * t % kM) % kM;
  }
  cout << ans;
  return 0;
}

2025/10/23

昨天晚上 1 点睡,一天都没有精神,什么都思考不了/kk

D.马自立

可以将题目转化为是否存在一个子图包含给定边,并且这个子图存在欧拉回路。然后脑子短路了去看 T2 了。

如果继续思考会想到存在欧拉回路等价于每个结点的度都为偶数,那么题目就是 允许删去若干不重要的边,能否使得所有结点的度变成偶数。

考虑给定边不能动,那么把这些边断开,并赋予每个点一个初始的奇偶性。考虑剩下的一些连通块,这里有一个套路。首先建出一颗生成树,如果一个点的度数是奇数,那么把他和父亲的边断开。如果到根的时候根的度数还是奇数,那么说明不可把每个点都变成偶度数。进一步思考发现每删去一条边会导致两个点的度数奇偶性发生改变,这样 度数为奇数的点的个数 的奇偶性不会发生改变。注意到对于不合法的点的个数为偶数时,一定存在一种方案使得所有点都能调整过来,这个方案类似一个传递到父亲的过程。

所以直接用并查集维护每一个连通块的不合法的点的奇偶性即可

Code
#include <iostream>
#include <numeric>
#include <vector>

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

const int kN = 5e5 + 1;

int T, n, m, fa[kN], sum[kN];
vector<pii> p;

int Find(int x) {
  return fa[x] == x ? fa[x] : fa[x] = Find(fa[x]);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("festival.in", "r", stdin);
  freopen("festival.out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> T;
  while (T--) {
    cin >> n >> m;
    fill(sum, sum + n + 1, -1), iota(fa + 1, fa + n + 1, 1);
    for (int i = 1, x, y, z; i <= m; i++) {
      cin >> x >> y >> z;
      sum[x] = max(sum[x], 0), sum[y] = max(sum[y], 0);
      if (z)
        sum[x] ^= 1, sum[y] ^= 1;
      else
        p.push_back({x, y});
    }
    for (auto it : p) {
      int x = Find(it.first), y = Find(it.second);
      if (x == y)
        continue;
      fa[y] = x, sum[x] ^= sum[y];
    }
    int ans = 1;
    for (int i = 1; i <= n; i++)
      if (i == fa[i])
        ans &= sum[i] <= 0;
    cout << (ans ? "Yes\n" : "No\n");
    for (int i = 1; i <= n; i++)
      p.clear();
  }
  return 0;
}

2025/10/24

P2824 [HEOI2016/TJOI2016] 排序

和之前的 P2839 [国家集训队] middle 做法差不多,都是二分答案然后将序列转化为 01 序列更方便 check。这种题竟然可以用二分答案,感觉很巧妙

Code
#include <iostream>

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

const int kN = 1e5 + 1, kI = -1;

int n, m, x, a[kN], b[kN];
struct Query {
  int x, l, r;
} q[kN];
struct Tr {
  int tag, cnt;
} tr[kN << 2];

void Func(int x, int l, int r, int k) {
  tr[x].cnt = (r - l + 1) * k, tr[x].tag = k;
}
void Pushdown(int x, int l, int r) {
  int m = (l + r) / 2;
  if (tr[x].tag != kI)
    Func(x * 2, l, m, tr[x].tag), Func(x * 2 + 1, m + 1, r, tr[x].tag), tr[x].tag = kI;
}
void Update(int nl, int nr, int k, int x = 1, int l = 1, int r = n) {
  if (nl > nr)
    return;
  if (nl <= l && r <= nr)
    return Func(x, l, r, k);
  Pushdown(x, l, r);
  int m = (l + r) / 2;
  if (nl <= m)
    Update(nl, nr, k, x * 2, l, m);
  if (nr > m)
    Update(nl, nr, k, x * 2 + 1, m + 1, r);
  tr[x].cnt = tr[x * 2].cnt + tr[x * 2 + 1].cnt;
}
int Query(int nl, int nr, int x = 1, int l = 1, int r = n) {
  if (nl <= l && r <= nr)
    return tr[x].cnt;
  Pushdown(x, l, r);
  int m = (l + r) / 2, res = 0;
  if (nl <= m)
    res = Query(nl, nr, x * 2, l, m);
  if (nr > m)
    res += Query(nl, nr, x * 2 + 1, m + 1, r);
  return res;
}
void Build(int x = 1, int l = 1, int r = n) {
  tr[x].tag = kI;
  if (l == r)
    return tr[x].cnt = b[l], void();
  int m = (l + r) / 2;
  Build(x * 2, l, m), Build(x * 2 + 1, m + 1, r);
  tr[x].cnt = tr[x * 2].cnt + tr[x * 2 + 1].cnt;
}

bool Check(int t) {
  for (int i = 1; i <= n; i++)
    b[i] = t <= a[i];
  Build();
  for (int i = 1; i <= m; i++) {
    int t = Query(q[i].l, q[i].r);
    if (q[i].x)
      Update(q[i].l, q[i].l + t - 1, 1), Update(q[i].l + t, q[i].r, 0);
    else
      Update(q[i].r - t + 1, q[i].r, 1), Update(q[i].l, q[i].r - t, 0);
  }
  return Query(x, x);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    cin >> a[i];
  for (int i = 1; i <= m; i++)
    cin >> q[i].x >> q[i].l >> q[i].r;
  cin >> x;
  int l = 1, r = n;
  for (int m = (l + r + 1) / 2; l < r; m = (l + r + 1) / 2) {
    if (Check(m))
      l = m;
    else
      r = m - 1;
  }
  cout << l;
  return 0;
}

Troubles

这么简单的题卡了我好久。

考虑把产生答案的 \((a_i, b_j)\) 放到二维平面上,很明显发现所有的这种点围成的矩形会包含所有 \((a_i, b_i)\),并且之间没有任何的偏序关系。所以当 \(b_j\) 递减的时候, \(a_i\) 一定递增,不然会产生偏序关系,答案一定不优。所以考虑按照 \(b_i\) 从大到小排序,此时只需要把 \(a_i\) 单调递增的点拿出来即可。

然后很明显的发现肯定是一段连续的区间在同一个矩形中,考虑设 \(f_i\) 表示考虑到第 \(i\) 个点,最优答案是多少。因为 \(b_i\)\(a_i\) 都是单调的,所以区间左端点的 \(b_i\) 最大,右端点的 \(a_i\) 最大,有转移 \(f_i = \min(f_{j - 1} + a_i * b_j)\)。这个东西可以用单调队列优化也可以用李超树,我想着练习一下李超树所以写的李超树

要注意值域是 \(10^6\) 不是 \(10^5\)

Code
#include <algorithm>
#include <iostream>

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

const int kN = 3e6 + 1, kV = 1e6;
const ll kI = 1e18;

int n;
pii a[kN];
ll ans, f[kN];
struct Line {
  ll k, b = kI;

  ll Calc(int x) { return k * x + b; }
} tr[kN * 4];
bool Cmp(Line a, Line b, int x) {
  return a.Calc(x) < b.Calc(x);
}
void Insert(Line v, int x = 1, int l = 1, int r = kV) {
  int m = (l + r) / 2;
  Cmp(v, tr[x], m) && (swap(v, tr[x]), 0);
  if (Cmp(v, tr[x], l))
    Insert(v, x * 2, l, m);
  else if (Cmp(v, tr[x], r))
    Insert(v, x * 2 + 1, m + 1, r);
}
ll Query(int p, int x = 1, int l = 1, int r = kV) {
  if (l == r)
    return tr[x].Calc(p);
  int m = (l + r) / 2;
  ll res = p <= m ? Query(p, x * 2, l, m) : Query(p, x * 2 + 1, m + 1, r);
  return min(res, tr[x].Calc(p));
}

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 >> a[i].first >> a[i].second;
  sort(a + 1, a + n + 1, [](pii x, pii y) { return x.second > y.second || (x.second == y.second && x.first > y.first); });
  for (int i = 1, s = 0; i <= n; i++) {
    if (a[i].first <= s) {
      f[i] = f[i - 1];
      continue;
    }
    Insert({a[i].second, f[i - 1]}), ans = f[i] = Query(a[i].first);
    s = a[i].first;
  }
  cout << ans;
  return 0;
}

2025/10/25

P4198 楼房重建

pushup 可以用递归维护的线段树,非常巧妙。

这题需要注意的是 pushup 的时候右边的子树记录的答案不等于整个子树中右子树产生的贡献,而是需要整个子树的答案减去左边子树产生的贡献。略微卡精度

Code
#include <iostream>

using namespace std;

const int kN = 1e5 + 1;
const double eps = 1e-10;

int n, m;
struct Tr {
  double k;
  int cnt;
} tr[kN * 4];

int Pushup(double k, int x, int l, int r) {
  if (l == r)
    return tr[x].k - k > eps && tr[x].cnt;
  int m = (l + r) / 2;
  if (k - tr[x * 2].k > eps)
    return Pushup(k, x * 2 + 1, m + 1, r);
  return Pushup(k, x * 2, l, m) + tr[x].cnt - tr[x * 2].cnt;
}
void Update(int p, double k, int x = 1, int l = 1, int r = n) {
  if (l == r)
    return tr[x].cnt = 1, tr[x].k = k, void();
  int m = (l + r) / 2;
  if (p <= m)
    Update(p, k, x * 2, l, m);
  else
    Update(p, k, x * 2 + 1, m + 1, r);
  tr[x].cnt = tr[x * 2].cnt + Pushup(tr[x * 2].k, x * 2 + 1, m + 1, r), tr[x].k = max(tr[x * 2].k, tr[x * 2 + 1].k);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int x, y; m--;) {
    cin >> x >> y;
    Update(x, 1.0 * y / x);
    cout << tr[1].cnt << '\n';
  }
  return 0;
}

H - Distinct Integers

主要是想到对于每个右端点,答案为 \(i - \max pre_j\),其中 \(pre_i\) 表示在 \(i\) 之前出现的最晚的 \(a_i\) 的位置

那么原问题转化为求前缀 \(max\) 的和,和上一题一样的做法即可

Code
#include <iostream>
#include <set>

using namespace std;
using ll = long long;

const int kN = 5e5 + 1;

int n, m, a[kN];
set<int> st[kN];
struct Tr {
  int mx;
  ll sum;
} tr[kN * 4];

ll Pushup(int k, int x, int l, int r) {
  if (l == r)
    return max(tr[x].mx, k);
  int m = (l + r) / 2;
  if (k >= tr[x * 2].mx)
    return Pushup(k, x * 2 + 1, m + 1, r) + 1ll * (m - l + 1) * k;
  return Pushup(k, x * 2, l, m) + tr[x].sum - tr[x * 2].sum;
}
void Update(int p, int k, int x = 1, int l = 1, int r = n) {
  if (l == r)
    return tr[x].mx = tr[x].sum = k, void();
  int m = (l + r) / 2;
  if (p <= m)
    Update(p, k, x * 2, l, m);
  else
    Update(p, k, x * 2 + 1, m + 1, r);
  tr[x].sum = tr[x * 2].sum + Pushup(tr[x * 2].mx, x * 2 + 1, m + 1, r), tr[x].mx = max(tr[x * 2].mx, tr[x * 2 + 1].mx);
}
ll Query(int k, int nl, int nr, int x = 1, int l = 1, int r = n) {
  if (nl <= l && r <= nr)
    return Pushup(k, x, l, r);
  int m = (l + r) / 2;
  ll res = 0;
  if (nl <= m)
    res = Query(k, nl, nr, x * 2, l, m);
  if (nr > m)
    res += Query(max(k, tr[x * 2].mx), nl, nr, x * 2 + 1, m + 1, r);
  return res;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("in", "r", stdin);
  freopen("out", "w", stdout);
#endif
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i < kN; i++)
    st[i].insert(0);
  for (int i = 1; i <= n; i++) {
    cin >> a[i], a[i]++;
    Update(i, *st[a[i]].rbegin()), st[a[i]].insert(i);
  }
  for (int op, x, y; m--;) {
    cin >> op >> x >> y, x++, y++;
    if (!op) {
      auto it = st[a[x]].find(x);
      if (next(it) != st[a[x]].end())
        Update(*next(it), *prev(it));
      st[a[x]].erase(x), a[x] = y;
      it = st[y].lower_bound(x);
      if (it != st[y].end())
        Update(*it, x);
      Update(x, *prev(it));
      st[y].insert(x);
    } else
      cout << 1ll * (x + y - 1) * (y - x) / 2 - Query(x - 1, x, y - 1) << '\n';
  }
  return 0;
}
posted @ 2025-10-20 22:49  sb-yyds  阅读(9)  评论(0)    收藏  举报