<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Unleafy‘s Blog]]></title><description><![CDATA[Coding...]]></description><link>https://unleafy.cn</link><image><url>https://unleafy.cn/favicon.svg</url><title>Unleafy‘s Blog</title><link>https://unleafy.cn</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Sun, 05 Apr 2026 07:42:32 GMT</lastBuildDate><atom:link href="https://unleafy.cn/feed" rel="self" type="application/rss+xml"/><pubDate>Sun, 05 Apr 2026 07:42:32 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[莫队]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/solution/mo-algorithm">https://unleafy.cn/posts/solution/mo-algorithm</a></blockquote><div><h1 id="">普通莫队</h1><h2 id="p2709----b-httpswwwluogucomcnproblemp2709"><a href="https://www.luogu.com.cn/problem/P2709">P2709 【模板】莫队 / 小 B 的询问</a></h2><p>我们开一个桶 $c_i$ 表示区间内某个颜色 $i$ 的出现次数，直接用莫队维护即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include&lt;bits/stdc++.h&gt;

using namespace std;

const int N = 1e6 + 5;
long long n, m, l, r, k;
long long a[N], pos[N], cnt[N], now, ans[N], L[N], R[N];

struct Query {
    int l, r, id;
}Q[N];

bool cmp(Query a, Query b) {
    if(pos[a.l] != pos[b.l]) return pos[a.l] &lt; pos[b.l];
    else if(pos[a.r] &amp; 1) return pos[a.r] &lt; pos[b.r];
    else return pos[a.r] &gt; pos[b.r];
}

void add(int x) {
    int val = cnt[a[x]];
    cnt[a[x]] ++;
    now = now - val * val + cnt[a[x]] * cnt[a[x]];
}

void del(int x) {
    int val = cnt[a[x]];
    cnt[a[x]] --;
    now = now - val * val + cnt[a[x]] * cnt[a[x]];
}

int main() {
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; k;
    
    int block = sqrt(n);
    int num = ceil((double) n / (double) block);
    
    for(int i = 1; i &lt;= num; i ++) {
        L[i] = (i - 1) * block + 1;
        R[i] = i * block;
        
        for(int j = L[i]; j &lt;= R[i]; j ++) {
            pos[j] = i;
        }
    }
    
    for(int i = 1; i &lt;= n; i ++) {
        cin &gt;&gt; a[i];
    }
    
    for(int i = 1; i &lt;= m; i ++) {
        cin &gt;&gt; Q[i].l &gt;&gt; Q[i].r;
        Q[i].id = i;
    }
    
    sort(Q + 1, Q + m + 1, cmp);
    
    int left = 1, right = 0;
    
    for(int i = 1; i &lt;= m; i ++) {
        int l = Q[i].l, r = Q[i].r, id = Q[i].id;
        
        while(left &lt; l) del(left ++);
        while(left &gt; l) add(-- left);
        while(right &lt; r) add(++ right);
        while(right &gt; r) del(right --);
        
        ans[id] = now;
    }
    
    for(int i = 1; i &lt;= m; i ++) cout &lt;&lt; ans[i] &lt;&lt; endl;
    
    return 0;
}</code></pre><h2 id="p1494---z-httpswwwluogucomcnproblemp1494"><a href="https://www.luogu.com.cn/problem/P1494">P1494 【国家集训队】 小 Z 的袜子</a></h2><p>我们考虑用莫队维护区间内抽到相同颜色的出现概率，用桶直接维护每种颜色的出现次数即可，注意输出格式要求输出最简分数。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

const int N = 5e4 + 5;
long long cnt[N], answer = 0;
int pos[N], a[N], n, m;
pair&lt;long long, long long&gt; result[N];

long long gcd(long long a, long long b) {
  if (b == 0) return a;
  return gcd(b, a % b);
}

struct Query {
  int l, r, id;
  
  bool operator &lt; (const Query &amp; rhv) const {
    if (pos[l] == pos[rhv.l]) return r &lt; rhv.r;
    return l &lt; rhv.l;
  }
} query[N];

void update(int pos, int add) {
  answer -= cnt[a[pos]] * cnt[a[pos]];
  cnt[a[pos]] += add;
  answer += cnt[a[pos]] * cnt[a[pos]];
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);

  cin &gt;&gt; n &gt;&gt; m;

  int block = sqrt(n);

  for (int i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    pos[i] = (i - 1) / block + 1;
  }

  for (int i = 1; i &lt;= m; i++) {
    cin &gt;&gt; query[i].l &gt;&gt; query[i].r;
    query[i].id = i;
  }

  sort(query + 1, query + m + 1);

  int L = 1, R = 0;

  for (int i = 1; i &lt;= m; i++) {
    while (L &lt; query[i].l) update(L, -1), L++;
    while (L &gt; query[i].l) L--, update(L, 1);
    while (R &lt; query[i].r) R++, update(R, 1);
    while (R &gt; query[i].r) update(R, -1), R--;

    if (query[i].l == query[i].r) {
      result[query[i].id] = make_pair(0, 1);
      continue;
    }
    
    long long a = answer - (query[i].r - query[i].l + 1);
    long long b = (long long) (query[i].r - query[i].l + 1) * (query[i].r - query[i].l);
    long long gcd_value = gcd(a, b);
    
    result[query[i].id] = make_pair(a / gcd_value, b / gcd_value);
  }
  
  for (int i = 1; i &lt;= m; i ++) {
    cout &lt;&lt; result[i].first &lt;&lt; &quot;/&quot; &lt;&lt; result[i].second &lt;&lt; &#x27;\n&#x27;;
  }

  return 0;
}</code></pre><h2 id="p4462-cqoi2018-httpswwwluogucomcnproblemp4462"><a href="https://www.luogu.com.cn/problem/P4462">P4462 【CQOI2018】 异或序列</a></h2><p>我们定义 $pre<em>i = \oplus^{i}</em>{j=1} a<em>i$，询问我们有多少组 $(x, y)$ 满足 $a_x \oplus a</em>{x+1} \oplus a<em>{x+2} \oplus \dots \oplus a</em>{y-2} \oplus a<em>{y-1} \oplus a_y = k$ 即为 $pre_y \oplus pre</em>{x-1} = k$，开桶统计即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

using i32 = int;
using i64 = long long;
using i128 = __int128;
using ui64 = unsigned long long;
using f32 = float;
using f64 = double;

constexpr i32 N = 1e5 + 5;

i32 n, m, a[N], k;
i32 l[N], r[N], pos[N], block, num;
i64 ans[N], sum[N], cur;
vector&lt;tuple&lt;i32, i32, i32&gt;&gt; queries;

inline void Add(i32 i) {
  cur += sum[a[i] ^ k];
  sum[a[i]]++;
}

inline void Del(i32 i) {
  sum[a[i]]--;
  cur -= sum[a[i] ^ k];
}

i32 main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; m &gt;&gt; k;
  
  for (i32 i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    a[i] ^= a[i - 1];
  }
  
  queries.resize(m);
  for (i32 i = 0; i &lt; m; i++) {
    cin &gt;&gt; get&lt;0&gt;(queries[i]) &gt;&gt; get&lt;1&gt;(queries[i]);
    get&lt;2&gt;(queries[i]) = i;
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (i32 i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = i * block;
    for (i32 j = l[i]; j &lt;= r[i]; j++) {
      pos[j] = i;
    }
  }
  
  sort(queries.begin(), queries.end(), [&amp;] (auto a, auto b) {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    else if (pos[get&lt;0&gt;(a)] &amp; 1)
      return get&lt;1&gt;(a) &gt; get&lt;1&gt;(b);
    else
      return get&lt;1&gt;(a) &lt; get&lt;1&gt;(b);
  });
  
  i32 L = 0, R = 0;
  sum[0] = 1;
  for (i32 i = 0; i &lt; (i32) queries.size(); i++) {
    while (L &lt; get&lt;0&gt;(queries[i]) - 1)
      Del(L++);
    while (L &gt; get&lt;0&gt;(queries[i]) - 1)
      Add(--L);
    while (R &lt; get&lt;1&gt;(queries[i]))
      Add(++R);
    while (R &gt; get&lt;1&gt;(queries[i]))
      Del(R--);
    
    ans[get&lt;2&gt;(queries[i])] = cur;
  }
  
  for (i32 i = 0; i &lt; m; i++) {
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h2 id="p3709-httpswwwluogucomcnproblemp3709"><a href="https://www.luogu.com.cn/problem/P3709">P3709 大爷的字符串题</a></h2><p>设 $c_i$ 表示当前区间内 $i$ 这个数字的出现次数，不难发现，rp 的最终结果只和区间内最大的 $c_i$ 有关，莫队维护即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

using i32 = int;
using i64 = long long;
using f32 = float;
using f64 = double;

constexpr i32 N = 2e5 + 5;

i32 n, m, a[N], ans[N];
i32 l[N], r[N], pos[N], block, num;
i32 tot[N], refer[N], cur = 0;
vector&lt;tuple&lt;i32, i32, i32&gt;&gt; queries;
vector&lt;int&gt; vars;

inline void Add(i32 i) {
  refer[tot[a[i]]]--;
  tot[a[i]]++;
  refer[tot[a[i]]]++;
  cur = max(cur, tot[a[i]]);
}

inline void Del(i32 i) {
  refer[tot[a[i]]]--;
  if (cur == tot[a[i]] &amp;&amp; refer[tot[a[i]]] == 0)
    cur--;
  tot[a[i]]--;
  refer[tot[a[i]]]++;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; m;
  
  for (i32 i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    vars.push_back(a[i]);
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (i32 i = 1; i &lt;= n; i++) {
    a[i] = (i32) (lower_bound(vars.begin(), vars.end(), a[i]) - vars.begin() + 1);
  }
  
  queries.resize(m);
  for (i32 i = 0; i &lt; m; i++) {
    cin &gt;&gt; get&lt;0&gt;(queries[i]) &gt;&gt; get&lt;1&gt;(queries[i]);
    get&lt;2&gt;(queries[i]) = i;
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (i32 i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(n, i * block);
    
    for (int j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(queries.begin(), queries.end(), [&amp;] (auto a, auto b) {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    if (pos[get&lt;0&gt;(a)] &amp; 1)
      return get&lt;1&gt;(a) &lt; get&lt;1&gt;(b);
    return get&lt;1&gt;(a) &gt; get&lt;1&gt;(b);
  });
  
  i32 L = 0, R = 0;
  refer[0] = n;
  for (i32 i = 0; i &lt; (i32) queries.size(); i++) {
    while (L &lt; get&lt;0&gt;(queries[i]))
      Del(L++);
    while (L &gt; get&lt;0&gt;(queries[i]))
      Add(--L);
    while (R &lt; get&lt;1&gt;(queries[i]))
      Add(++R);
    while (R &gt; get&lt;1&gt;(queries[i]))
      Del(R--);
    
    ans[get&lt;2&gt;(queries[i])] = -cur;
  }
  
  for (i32 i = 0; i &lt; m; i++) {
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h1 id="">带修莫队</h1><p>带修莫队就是在普通莫队的基础上加上了一个时间维度，我们只要记录查询的时间，并将之前的操作全部应用即可，注意这时候块长取 $n^{\frac{3}{2}}$ 可以取到最优复杂度。</p>
<h2 id="p1903------httpswwwluogucomcnproblemp1903"><a href="https://www.luogu.com.cn/problem/P1903">P1903 【模板】带修莫队 / 【国家集训队】 数颜色 / 维护队列</a></h2><p>我们维护一个时间维度上的修改操作即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

#ifdef LOCAL
#include &lt;algo/debug.h&gt;
#else
#define debug(...) 42
#endif

using namespace std;

constexpr int N = 6e5 + 5;

int n, m, c[N], cpy[N], lst[N], answer[N];
int cnt_queries = 0;
set&lt;int&gt; positions[N];
vector&lt;int&gt; vars;

int cnt_op = 0;
struct Op {
  int type, x, r, y, val, index;
} ops[N];

struct Fenwick {
  int tr[N], Limit = 0;
  Fenwick() = default;
  Fenwick(int n) : Limit(n) {
    fill(tr, tr + Limit + 1, 0);
  }
  static int Lowbit(int x) {
    return x &amp; (-x);
  }
  void Modify(int pos, int var) {
    for (int i = pos; i &lt;= Limit; i += Lowbit(i))
      tr[i] += var;
  }
  int Query(int pos) {
    int retval = 0;
    for (int i = pos; i; i -= Lowbit(i))
      retval += tr[i];
    return retval;
  }
} tr;

int GetColor(int col) {
  return (int) (lower_bound(vars.begin(), vars.end(), col) - vars.begin() + 1);
}

void CDQ(int l, int r) {
  if (l == r) return;
  
  int mid = (l + r) &gt;&gt; 1;
  CDQ(l, mid);
  CDQ(mid + 1, r);
  
  sort(ops + l, ops + mid + 1, [&amp;] (auto a, auto b) {
    if (a.y != b.y) return a.y &lt; b.y;
    return a.x &lt; b.x;
  });
  sort(ops + mid + 1, ops + r + 1, [&amp;] (auto a, auto b) {
    if (a.y != b.y) return a.y &lt; b.y;
    return a.x &lt; b.x;
  });
  
  int pl = l, pr = mid + 1;
  while (pr &lt;= r) {
    while (pl &lt;= mid &amp;&amp; ops[pl].y &lt; ops[pr].y) {
      if (ops[pl].type == 1) {
        tr.Modify(ops[pl].x, ops[pl].val);
      }
      pl++;
    }
    
    if (ops[pr].type == 2) {
      answer[ops[pr].index] += tr.Query(ops[pr].r) - tr.Query(ops[pr].x - 1);
    }
    pr++;
  }
  
  for (int i = l; i &lt; pl; i++) {
    if (ops[i].type == 1) {
      tr.Modify(ops[i].x, -ops[i].val);
    }
  }
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; m;
  
  for (int i = 1; i &lt;= n; i++) {
    cin &gt;&gt; c[i];
    cpy[i] = c[i];
    vars.push_back(c[i]);
  }
  
  vector&lt;tuple&lt;char, int, int&gt;&gt; raw(m + 1);
  for (int i = 1; i &lt;= m; i++) {
    auto&amp;&amp; [x, y, z] = raw[i];
    cin &gt;&gt; x &gt;&gt; y &gt;&gt; z;
    
    if (x == &#x27;R&#x27;) {
      vars.push_back(z);
    }
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (int i = 1; i &lt;= n; i++) {
    positions[GetColor(c[i])].insert(i);
  }
  
  for (int i = 1; i &lt;= n; i++) {
    int cur_col = GetColor(c[i]);
    ops[++cnt_op] = Op{1, i, 0, lst[cur_col], 1, 0};
    lst[cur_col] = i;
  }
  
  for (int i = 1; i &lt;= m; i++) {
    if (get&lt;0&gt;(raw[i]) == &#x27;Q&#x27;) {
      auto&amp;&amp; [op, l, r] = raw[i];
      
      ops[++cnt_op] = Op{2, l, r, l, 0, ++cnt_queries};
    } else if (get&lt;0&gt;(raw[i]) == &#x27;R&#x27;) {
      auto&amp;&amp; [op, pos, nxt_col] = raw[i];
      int cur_col = cpy[pos];
      
      auto Current = positions[GetColor(cur_col)].find(pos);
      int pre = 0, suc = 0;
      if (Current != positions[GetColor(cur_col)].begin()) {
        pre = *prev(Current);
      }
      if (next(Current) != positions[GetColor(cur_col)].end()) {
        suc = *next(Current);
      }
      
      ops[++cnt_op] = Op{1, pos, 0, pre, -1, 0};
      
      if (suc != 0) {
        ops[++cnt_op] = Op{1, suc, 0, pos,  -1, 0};
        ops[++cnt_op] = Op{1, suc, 0, pre, 1, 0};
      }
      positions[GetColor(cur_col)].erase(Current);
      
      Current = positions[GetColor(nxt_col)].lower_bound(pos);
      pre = 0, suc = 0;
      if (Current != positions[GetColor(nxt_col)].begin()) {
        pre = *prev(Current);
      }
      if (Current != positions[GetColor(nxt_col)].end()) {
        suc = *Current;
      }
      
      ops[++cnt_op] = Op{1, pos, 0, pre, 1, 0};
      
      if (suc != 0) {
        ops[++cnt_op] = Op{1, suc, 0, pre, -1, 0};
        ops[++cnt_op] = Op{1, suc, 0, pos, 1, 0};
      }
      positions[GetColor(nxt_col)].insert(pos);
      
      cpy[pos] = nxt_col;
    }
  }
  
  tr = Fenwick(n);
  CDQ(1, cnt_op);
  
  for (int i = 1; i &lt;= cnt_queries; i++) {
    cout &lt;&lt; answer[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h2 id="problem---940f---codeforceshttpscodeforcescomproblemsetproblem940f"><a href="https://codeforces.com/problemset/problem/940/F">Problem - 940F - Codeforces</a></h2><p>直接开桶暴力维护mex即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

using i32 = int;
using i64 = long long;
using f32 = float;
using f64 = double;

constexpr i32 N = 5e5 + 5;

i32 n, q, a[N], ans[N];
i32 l[N], r[N], pos[N], block, num;
vector&lt;tuple&lt;i32, i32, i32&gt;&gt; query, modify;
vector&lt;i32&gt; vars;
i32 cnt[N], tot[N];

inline void Add(i32 val) {
  if (cnt[val] &gt; 0) tot[cnt[val]]--;
  cnt[val]++;
  if (cnt[val] &gt; 0) tot[cnt[val]]++;
}

inline void Del(i32 val) {
  if (cnt[val] &gt; 0) tot[cnt[val]]--;
  cnt[val]--;
  if (cnt[val] &gt; 0) tot[cnt[val]]++;
}

inline void Upd(i32 idx_q, i32 idx_m) {
  if (get&lt;0&gt;(modify[idx_m]) &gt;= get&lt;0&gt;(query[idx_q]) &amp;&amp; get&lt;0&gt;(modify[idx_m]) &lt;= get&lt;1&gt;(query[idx_q])) {
    Del(a[get&lt;0&gt;(modify[idx_m])]);
    Add(get&lt;1&gt;(modify[idx_m]));
  }
  swap(get&lt;1&gt;(modify[idx_m]), a[get&lt;0&gt;(modify[idx_m])]);
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; q;
  
  for (i32 i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    vars.push_back(a[i]);
  }
  
  for (i32 i = 1; i &lt;= q; i++) {
    i32 op, l, r;
    cin &gt;&gt; op &gt;&gt; l &gt;&gt; r;
    
    if (op == 1) {
      query.push_back(make_tuple(l, r, i));
    } else {
      modify.push_back(make_tuple(l, r, i));
      vars.push_back(r);
    }
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (i32 i = 1; i &lt;= n; i++) {
    a[i] = lower_bound(vars.begin(), vars.end(), a[i]) - vars.begin() + 1;
  }
  for (i32 i = 0; i &lt; (i32) modify.size(); i++) {
    get&lt;1&gt;(modify[i]) = lower_bound(vars.begin(), vars.end(), get&lt;1&gt;(modify[i])) - vars.begin() + 1;
  }
  
  block = pow(n, 2.0 / 3);
  num = (n + block - 1) / block;
  
  for (i32 i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(i * block, n);
    
    for (i32 j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(query.begin(), query.end(), [&amp;] (auto a, auto b) {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    if (pos[get&lt;1&gt;(a)] != pos[get&lt;1&gt;(b)])
      return pos[get&lt;1&gt;(a)] &lt; pos[get&lt;1&gt;(b)];
    return get&lt;2&gt;(a) &lt; get&lt;2&gt;(b);
  });
  sort(modify.begin(), modify.end(), [&amp;] (auto a, auto b) {
    return get&lt;2&gt;(a) &lt; get&lt;2&gt;(b);
  });
  
  i32 L = 0, R = 0, idx = -1;
  for (i32 i = 0; i &lt; (i32) query.size(); i++) {
    while (L &lt; get&lt;0&gt;(query[i]))
      Del(a[L++]);
    while (L &gt; get&lt;0&gt;(query[i]))
      Add(a[--L]);
    while (R &lt; get&lt;1&gt;(query[i]))
      Add(a[++R]);
    while (R &gt; get&lt;1&gt;(query[i]))
      Del(a[R--]);
    while (idx + 1 &lt; (i32) modify.size() &amp;&amp; get&lt;2&gt;(modify[idx + 1]) &lt; get&lt;2&gt;(query[i]))
      Upd(i, ++idx);
    while (idx &gt;= 0 &amp;&amp; get&lt;2&gt;(modify[idx]) &gt; get&lt;2&gt;(query[i]))
      Upd(i, idx--);
    
    ans[get&lt;2&gt;(query[i])] = 1;
    while (tot[ans[get&lt;2&gt;(query[i])]] &gt; 0) ans[get&lt;2&gt;(query[i])]++;
  }
  
  for (int i = 1; i &lt;= q; i++) {
    if (ans[i]) cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h1 id="">回滚莫队</h1><p>对于一些加入元素很难，但是删除元素很简单的题目，我们可以考虑只维护插入的元素，然后处理完当前询问后直接继承之前的状态即可，这样可以保证复杂度为 $\Theta(\sqrt{n})$。反之亦然。</p>
<h2 id="p5906-httpswwwluogucomcnproblemp5906"><a href="https://www.luogu.com.cn/problem/P5906">P5906 【模板】回滚莫队&amp;不删除莫队</a></h2><p>板子题。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

constexpr int N = 4e5 + 5;

int n, m, a[N];
int l[N], r[N], pos[N], block, num;
int max_pos[N], min_pos[N], _max_pos[N];
int max_dist, ans[N];
vector&lt;tuple&lt;int, int, int&gt;&gt; queries;
vector&lt;int&gt; vars;

inline int BruteQuery(int l, int r) {
  static int mx[N], mn[N];
  int res = 0;
  for (int i = l; i &lt;= r; i++) {
    if (!mn[a[i]]) mn[a[i]] = i;
    mx[a[i]] = i;
    res = max(res, mx[a[i]] - mn[a[i]]);
  }
  
  for (int i = l; i &lt;= r; i++)
    mx[a[i]] = mn[a[i]] = 0;
  
  return res;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n;
  
  for (int i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    vars.push_back(a[i]);
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (int i = 1; i &lt;= n; i++) {
    a[i] = lower_bound(vars.begin(), vars.end(), a[i]) - vars.begin() + 1;
  }
  
  cin &gt;&gt; m;
  for (int i = 1; i &lt;= m; i++) {
    int left, right;
    cin &gt;&gt; left &gt;&gt; right;
    queries.push_back(make_tuple(left, right, i));
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (int i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(i * block, n);
    
    for (int j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(queries.begin(), queries.end(), [&amp;] (auto a, auto b) {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    return get&lt;1&gt;(a) &lt; get&lt;1&gt;(b);
  });
  
  int query_idx = 0;
  for (int block_idx = 1; block_idx &lt;= num; block_idx++) {
    max_dist = 0;
    int L = r[block_idx] + 1, R = r[block_idx];
    memset(max_pos, 0, sizeof(max_pos));
    memset(min_pos, 0, sizeof(min_pos));
    
    while (query_idx &lt; (int) queries.size() &amp;&amp; pos[get&lt;0&gt;(queries[query_idx])] == block_idx) {
      auto&amp;&amp; [ql, qr, idx] = queries[query_idx];
      
      if (qr &lt;= r[block_idx]) {
        ans[idx] = BruteQuery(ql, qr);
      } else {
        while (R &lt; qr) {
          R++;
          if (!min_pos[a[R]]) min_pos[a[R]] = R;
          max_pos[a[R]] = R;
          max_dist = max(max_dist, max_pos[a[R]] - min_pos[a[R]]);
        }
        
        int _max_dist = 0, _L = L;
        while (L &gt; ql) {
          L--;
          if (!_max_pos[a[L]]) _max_pos[a[L]] = L;
          _max_dist = max(_max_dist, max(_max_pos[a[L]], max_pos[a[L]]) - L);
        }
        
        ans[idx] = max(_max_dist, max_dist);
        
        while (L &lt; _L)
          _max_pos[a[L++]] = 0;
      }
      
      query_idx++;
    }
  }
  
  for (int i = 1; i &lt;= m; i++) {
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h2 id="p14420-joisc-2014---historical-researchhttpswwwluogucomcnproblemp14420"><a href="https://www.luogu.com.cn/problem/P14420">P14420 【JOISC 2014】 历史的研究 / Historical Research</a></h2><p>容易发现删除的时候最大值不连续，所以删除不太好维护，我们考虑使用不删除莫队维护，每次回滚状态即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

#ifdef LOCAL
#include &lt;algo/debug.h&gt;
#else
#define debug(...) 42
#endif

using namespace std;

using i32 = int;
using i64 = long long;
using f32 = float;
using f64 = double;

constexpr i32 N = 3e5 + 5;

i32 n, q, x[N];
i32 l[N], r[N], pos[N], block, num;
i64 cnt[N], ans[N], cur_max = 0;
vector&lt;tuple&lt;i32, i32, i32&gt;&gt; queries;
vector&lt;i32&gt; vars;

inline void Add(i32 val) {
  cnt[val]++;
  cur_max = max(cur_max, (i64) vars[val - 1] * cnt[val]);
}

inline void Del(i32 val) {
  cnt[val]--;
}

inline i64 BruteQuery(i32 L, i32 R) {
  i64 max_var = 0;
  map&lt;i32, i32&gt; cnt;
  for (i32 i = L; i &lt;= R; i++) {
    cnt[x[i]]++;
    max_var = max(max_var, (i64) vars[x[i] - 1] * cnt[x[i]]);
  }
  return max_var;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; q;
  
  for (i32 i = 1; i &lt;= n; i++) {
    cin &gt;&gt; x[i];
    vars.push_back(x[i]);
  }
  
  for (i32 i = 1; i &lt;= q; i++) {
    i32 left, right;
    cin &gt;&gt; left &gt;&gt; right;
    queries.push_back(make_tuple(left, right, i));
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (i32 i = 1; i &lt;= n; i++) {
    x[i] = lower_bound(vars.begin(), vars.end(), x[i]) - vars.begin() + 1;
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (i32 i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(n, i * block);
    
    for (i32 j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  for (i32 i = 1; i &lt;= n; i++)
    debug(pos[i]);
  
  sort(queries.begin(), queries.end(), [&amp;] (auto a, auto b) {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    return get&lt;1&gt;(a) &lt; get&lt;1&gt;(b);
  });
  
  i32 query_idx = 0;
  for (i32 block_idx = 1; block_idx &lt;= num; block_idx++) {
    cur_max = 0;
    i32 L = r[block_idx] + 1, R = r[block_idx];
    
    fill(cnt, cnt + (i32) vars.size() + 5, 0);
    
    while (query_idx &lt; (i32) queries.size() &amp;&amp; pos[get&lt;0&gt;(queries[query_idx])] == block_idx) {
      debug(L, R, cur_max, query_idx);
      
      auto&amp;&amp; [ql, qr, idx] = queries[query_idx];
      
      if (qr &lt;= r[block_idx]) {
        ans[idx] = BruteQuery(ql, qr);
      } else {
        while (R &lt; qr) Add(x[++R]);
        i64 _cur_max = cur_max;
        while (L &gt; ql) Add(x[--L]);
        
        ans[idx] = cur_max;
        
        while (L &lt;= r[block_idx]) Del(x[L++]);
        cur_max = _cur_max;
      }
      
      query_idx++;
    }
  }
  
  for (i32 i = 1; i &lt;= q; i++) {
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h2 id="p8078-wc2022-httpswwwluogucomcnproblemp8078"><a href="https://www.luogu.com.cn/problem/P8078">P8078 【WC2022】 秃子酋长</a></h2><p>发现删除好做加入难，使用回滚莫队维护。用链表维护当前的序列即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

namespace IO {
  template&lt;class _tp&gt; inline void read(_tp&amp; x) {
    x = 0; _tp f = 1; char ch = getchar_unlocked();
    while (!isdigit(ch)) { if (ch == &#x27;-&#x27;) f = -1; ch = getchar_unlocked(); }
    while (isdigit(ch)) { x = (x &lt;&lt; 1) + (x &lt;&lt; 3) + (ch ^ 48); ch = getchar_unlocked(); }
    x *= f;
  }
  template&lt;class _tp, class... args&gt; inline void read(_tp&amp; x, args&amp; ...t) {
    read(x);
    read(t...);
  }
  template&lt;class _tp&gt; inline void _write(_tp x) {
    if (x &lt; 0) putchar_unlocked(&#x27;-&#x27;), x = -x;
    if (x &gt; 9) _write(x / 10);
    putchar_unlocked(x % 10 + &#x27;0&#x27;);
  }
  template&lt;class _tp&gt; inline void write(_tp x, char split = &#x27;\n&#x27;) {
    _write(x);
    putchar(split);
  }
} using namespace IO;

constexpr int N = 5e5 + 5;

int n, m, a[N], refer[N];
int pos[N], l[N], r[N], block, num;
long long ans[N];

struct Query {
  int l, r, idx;
  Query() = default;
  Query(int l, int r, int i) : l(l), r(r), idx(i) {}
  bool operator&lt;(const Query&amp; rhs) const {
    if (pos[l] != pos[rhs.l])
      return pos[l] &lt; pos[rhs.l];
    return r &gt; rhs.r;
  }
} query[N];

stack&lt;int&gt; stk;

struct Link_t {
  int _prev, _next;
  Link_t() = default;
  Link_t(int l, int r) : _prev(l), _next(r) {}
  inline int&amp; prev() { return _prev; }
  inline int&amp; next() { return _next; }
} Link[N];

inline long long Del(int p) {
  long long cur = 0;
  if (Link[p].prev() != 0 &amp;&amp; Link[p].next() != 0)
    cur += abs(refer[Link[p].prev()] - refer[Link[p].next()]);
  if (Link[p].prev() != 0)
    cur -= abs(refer[p] - refer[Link[p].prev()]);
  if (Link[p].next() != 0)
    cur -= abs(refer[p] - refer[Link[p].next()]);
  
  stk.push(p);
  Link[Link[p].prev()].next() = Link[p].next();
  Link[Link[p].next()].prev() = Link[p].prev();
  
  return cur;
}

inline void Undo() {
  int p = stk.top();
  stk.pop();
  Link[Link[p].prev()].next() = p;
  Link[Link[p].next()].prev() = p;
}

int main() {
  // cin &gt;&gt; n &gt;&gt; m;
  read(n, m);
  
  for (int i = 1; i &lt;= n; i++) {
    // cin &gt;&gt; a[i];
    read(a[i]);
    refer[a[i]] = i;
  }
  
  for (int i = 1; i &lt;= n; i++) {
    Link[i].prev() = i - 1;
    Link[i].next() = i + 1;
  }
  Link[n].next() = 0;
  
  for (int i = 1; i &lt;= m; i++) {
    // cin &gt;&gt; query[i].l &gt;&gt; query[i].r;
    read(query[i].l, query[i].r);
    query[i].idx = i;
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (int i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(n, i * block);
    
    for (int j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(query + 1, query + m + 1);
  
  int L = 1, R = n, lst_block = 1;
  long long tmp = 0, cur = 0;
  
  for (int i = 2; i &lt;= n; i++) {
    cur += abs(refer[i] - refer[i - 1]);
  }
  
  tmp = cur;
  
  for (int i = 1; i &lt;= m; i++) {
    auto&amp;&amp; [ql, qr, idx] = query[i];
    
    if (pos[ql] != lst_block) {
      while (!stk.empty()) Undo();
      while (L &lt; l[pos[ql]]) cur += Del(a[L++]);
      tmp = cur;
      R = n;
      lst_block = pos[ql];
      while (!stk.empty()) stk.pop();
    }
    
    while (R &gt; qr) tmp += Del(a[R--]);
    
    long long _tmp = tmp, _L = L;
    while (_L &lt; ql) _tmp += Del(a[_L++]);
    while (_L &gt; L) Undo(), _L--;
    
    ans[idx] = _tmp;
  }
  
  for (int i = 1; i &lt;= m; i++) {
    // cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
    write(ans[i]);
  }
  
  return 0;
}</code></pre><h1 id="">树上莫队</h1><p>我们维护一个括号序列，在 dfs 的时候每次访问和回溯都加入到括号序列当中，定义 $in_u$ 和 $out_u$ 表示 $u$ 节点进入和退出 dfs 时在括号序列的位置，每次查询 $(u,v)$ 分为两种情况：</p><ul><li><p>直链：对应的括号序列的区间为 $in_u \sim in_v$，中间若同一个节点出现两次，则正负抵消，因为这样的节点一定不在 $u \to v$ 的路径上。</p></li><li><p>折链：用树剖的思想，将 $u \to v$ 转化为 $u \to \mathrm{LCA}(u, v)$ 和 $v \to \mathrm{LCA}(u, v)$，这样就得到了两条直链，直接维护即可。</p></li></ul><h2 id="p4074-wc2013-httpswwwluogucomcnproblemp4074"><a href="https://www.luogu.com.cn/problem/P4074">P4074 【WC2013】 糖果公园</a></h2><p>模板题。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

constexpr int N = 5e5 + 5;
constexpr int K = 25;

int n, m, q, v[N], w[N], c[N], lst[N];
int l[N], r[N], pos[N], block, num;
int seq[N], refer[N], in[N], out[N], cnt = 0;
int jmp[N][K], dep[N];
int f[N], tot[N];
long long cur, ans[N];
vector&lt;int&gt; adj[N];
vector&lt;tuple&lt;int, int, int, int&gt;&gt; query, modify;

inline void Dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    seq[++cnt] = u;
    in[u] = cnt;
    jmp[u][0] = fa;
    
    for (int k = 1; k &lt; K; k++)
        jmp[u][k] = jmp[jmp[u][k - 1]][k - 1];
    
    for (auto v : adj[u]) {
        if (v == fa) continue;
        Dfs(v, u);
    }
    
    seq[++cnt] = u;
    out[u] = cnt;
}

inline int GetLCA(int u, int v) {
    if (dep[u] &gt; dep[v]) swap(u, v);
    
    for (int k = K - 1; k &gt;= 0; k--) {
        if (dep[jmp[v][k]] &gt;= dep[u])
            v = jmp[v][k];
    }
    
    if (u == v)
        return u;
    
    for (int k = K - 1; k &gt;= 0; k--) {
        if (jmp[u][k] != jmp[v][k])
            tie(u, v) = tie(jmp[u][k], jmp[v][k]);
    }
    
    return jmp[u][0];
}

inline void Chg(int pos) {
    if (f[pos]) {
        cur -= (long long) v[c[pos]] * w[tot[c[pos]]];
        tot[c[pos]]--;
    } else {
        tot[c[pos]]++;
        cur += (long long) v[c[pos]] * w[tot[c[pos]]];
    }
    f[pos] ^= 1;
}

inline void Upd(int x, int t) {
    if (f[x]) {
        Chg(x);
        c[x] = t;
        Chg(x);
    } else {
        c[x] = t;
    }
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    
    // freopen(&quot;P4074_4.in&quot;, &quot;r&quot;, stdin);
    
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; q;
    
    for (int i = 1; i &lt;= m; i++) {
        cin &gt;&gt; v[i];
    }
    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; w[i];
    }
    
    for (int i = 1; i &lt; n; i++) {
        int u, v;
        cin &gt;&gt; u &gt;&gt; v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    
    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; c[i];
        lst[i] = c[i];
    }
    
    Dfs(1, 0);
    
    memset(ans, -1, sizeof(ans));
    for (int i = 1; i &lt;= q; i++) {
        int typ, x, y;
        cin &gt;&gt; typ &gt;&gt; x &gt;&gt; y;
        
        if (typ == 0) {
            modify.push_back(tuple(x, lst[x], y, i));
            lst[x] = y;
        } else if (typ == 1) {
            if (in[x] &gt; in[y])
                swap(x, y);
            query.push_back(tuple(GetLCA(x, y) == x ? in[x] : out[x], in[y], i, i));
        }
    }
    
    block = pow(2 * n, 2.0 / 3);
    num = (2 * n + block - 1) / block;
    
    for (int i = 1; i &lt;= num; i++) {
        l[i] = (i - 1) * block + 1;
        r[i] = min(2 * n, i * block);
        
        for (int j = l[i]; j &lt;= r[i]; j++)
            pos[j] = i;
    }
    
    sort(query.begin(), query.end(), [&amp;] (auto a, auto b) {
        if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
            return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
        if (pos[get&lt;1&gt;(a)] != pos[get&lt;1&gt;(b)])
            return pos[get&lt;1&gt;(a)] &lt; pos[get&lt;1&gt;(b)];
        return get&lt;2&gt;(a) &lt; get&lt;2&gt;(b);
    });
    // sort(modify.begin(), modify.end(), [&amp;] (auto a, auto b) {
    //     return get&lt;3&gt;(a) &lt; get&lt;3&gt;(b);
    // });
    
    int L = 1, R = 0, T = -1;
    
    for (int i = 0; i &lt; (int) query.size(); i++) {
        auto&amp;&amp; [ql, qr, idx, _] = query[i];
        
        while (!modify.empty() &amp;&amp; T + 1 &lt; (int) modify.size() &amp;&amp; get&lt;3&gt;(modify[T + 1]) &lt; idx) {
            T++;
            Upd(get&lt;0&gt;(modify[T]), get&lt;2&gt;(modify[T]));
        }
        while (!modify.empty() &amp;&amp; T &gt;= 0 &amp;&amp; get&lt;3&gt;(modify[T]) &gt; idx) {
            Upd(get&lt;0&gt;(modify[T]), get&lt;1&gt;(modify[T]));
            T--;
        }
        
        while (L &gt; ql) Chg(seq[--L]);
        while (L &lt; ql) Chg(seq[L++]);
        while (R &gt; qr) Chg(seq[R--]);
        while (R &lt; qr) Chg(seq[++R]);
        
        int x = seq[ql], y = seq[qr];
        int LCA = GetLCA(x, y);
        
        if (x != LCA &amp;&amp; y != LCA) {
            Chg(LCA);
            ans[idx] = cur;
            Chg(LCA);
        } else {
            ans[idx] = cur;
        }
    }
    
    for (int i = 1; i &lt;= q; i++) {
        if (ans[i] == -1) continue;
        cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
    }
    
    return 0;
}</code></pre><h2 id="p4689-ynoi-easy-round-2016----httpswwwluogucomcnproblemp4689"><a href="https://www.luogu.com.cn/problem/P4689">P4689 【Ynoi Easy Round 2016】 这是我自己的发明 - 洛谷</a></h2><p>用类似 <a href="https://www.luogu.com.cn/problem/P5268">P5268</a> 的方式将映射在括号序上的询问拆分一下即可。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

constexpr int N = 1e5 + 5;
constexpr int K = 25;
constexpr int M = 5e5 + 5;

int n, m, a[N];
int l[N], r[N], pos[N], block, num;
int dfn[N], t = 0, rf[N], dep[N], siz[N];
int jmp[N][K];
int cl[N], cr[N];
long long ans[M], cur;
vector&lt;int&gt; vars, adj[N];

struct Query {
  int ql, qr, f, idx;
  bool operator&lt;(const Query&amp; rhs) const {
    if (pos[ql] != pos[rhs.ql])
      return pos[ql] &lt; pos[rhs.ql];
    return qr &lt; rhs.qr;
  }
};
vector&lt;Query&gt; query;

inline void Dfs(int u, int father) {
  dep[u] = dep[father] + 1;
  jmp[u][0] = father;
  siz[u] = 1;
  dfn[u] = ++t;
  for (int k = 1; k &lt; K; k++)
    jmp[u][k] = jmp[jmp[u][k - 1]][k - 1];
  
  for (auto&amp;&amp; v : adj[u]) {
    if (v == father) continue;
    Dfs(v, u);
    siz[u] += siz[v];
  }
}

inline int GetKth(int u, int kth) {
  for (int k = K - 1; k &gt;= 0; k--) {
    if (kth &gt;&gt; k &amp; 1)
      u = jmp[u][k];
  }
  return u;
}

inline vector&lt;pair&lt;int, int&gt;&gt; GetRanges(int u, int root) {
  vector&lt;pair&lt;int, int&gt;&gt; ranges;
  if (u == root) {
    ranges.push_back(pair(1, t));
  } else if (dfn[root] &gt;= dfn[u] &amp;&amp; dfn[root] &lt;= dfn[u] + siz[u] - 1) {
    int kth = GetKth(root, dep[root] - dep[u] - 1);
    if (dfn[kth] - 1 &gt;= 1)
      ranges.push_back(pair(1, dfn[kth] - 1));
    if (dfn[kth] + siz[kth] &lt;= t)
      ranges.push_back(pair(dfn[kth] + siz[kth], t));
  } else {
    ranges.push_back(pair(dfn[u], dfn[u] + siz[u] - 1));
  }
  
  return ranges;
}

inline void UpdL(int var, int f) {
  cur += f * cr[var];
  cl[var] += f;
}

inline void UpdR(int var, int f) {
  cur += f * cl[var];
  cr[var] += f;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n &gt;&gt; m;
  
  for (int i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
    vars.push_back(a[i]);
  }
  
  sort(vars.begin(), vars.end());
  vars.erase(unique(vars.begin(), vars.end()), vars.end());
  
  for (int i = 1, u, v; i &lt; n; i++) {
    cin &gt;&gt; u &gt;&gt; v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  
  Dfs(1, 0);
  
  for (int i = 1; i &lt;= n; i++) {
    rf[dfn[i]] = (int) (lower_bound(vars.begin(), vars.end(), a[i]) - vars.begin() + 1);
  }
  
  memset(ans, -1, sizeof(ans));
  
  int root = 1;
  for (int i = 1; i &lt;= m; i++) {
    int tp, x, y;
    cin &gt;&gt; tp;
    
    if (tp == 1) {
      cin &gt;&gt; x;
      root = x;
    } else {
      cin &gt;&gt; x &gt;&gt; y;
      auto a = GetRanges(x, root), b = GetRanges(y, root);
      
      ans[i] = 0;
      for (auto&amp;&amp; va : a) {
        for (auto&amp;&amp; vb : b) {
          query.push_back({va.second, vb.second, 1, i});
          query.push_back({va.first - 1, vb.second, -1, i});
          query.push_back({va.second, vb.first - 1, -1, i});
          query.push_back({va.first - 1, vb.first - 1, 1, i});
        }
      }
    }
  }
  
  for (auto&amp;&amp; [ql, qr, f, idx] : query) {
    if (ql &gt; qr) swap(ql, qr);
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (int i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = min(t, i * block);
    
    for (int j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(query.begin(), query.end());
  
  int L = 0, R = 0;
  for (auto&amp;&amp; [ql, qr, f, idx] : query) {
    while (L &lt; ql) UpdL(rf[++L], 1);
    while (L &gt; ql) UpdL(rf[L--], -1);
    while (R &lt; qr) UpdR(rf[++R], 1);
    while (R &gt; qr) UpdR(rf[R--], -1);
    
    ans[idx] += cur * f;
  }
  
  for (int i = 1; i &lt;= m; i++) {
    if (ans[i] == -1) continue;
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre><h2 id="p5268-snoi2017-httpswwwluogucomcnproblemp5268"><a href="https://www.luogu.com.cn/problem/P5268">P5268 【SNOI2017】 一个简单的询问</a></h2><p>容易发现 $\operatorname{get}(l, r, x) = \operatorname{get}(r, 1, x) - \operatorname{get}(l - 1, 1, x)$，直接拆式子分两段维护即可。注意莫队双指针的移动方式。</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

constexpr int N = 5e4 + 5;

int n, q, a[N];
int l[N], r[N], pos[N], block, num;
int cl[N], cr[N];
long long ans[N], cur;
vector&lt;tuple&lt;int, int, int, int&gt;&gt; query;

inline void UpdL(int val, int f) {
  cur += f * cr[val];
  cl[val] += f;
}

inline void UpdR(int val, int f) {
  cur += f * cl[val];
  cr[val] += f;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  
  cin &gt;&gt; n;
  
  for (int i = 1; i &lt;= n; i++) {
    cin &gt;&gt; a[i];
  }
  
  cin &gt;&gt; q;
  for (int i = 1; i &lt;= q; i++) {
    int l1, r1, l2, r2;
    cin &gt;&gt; l1 &gt;&gt; r1 &gt;&gt; l2 &gt;&gt; r2;
    
    query.push_back(tuple(r1, r2, 1, i));
    query.push_back(tuple(l1 - 1, r2, -1, i));
    query.push_back(tuple(r1, l2 - 1, -1, i));
    query.push_back(tuple(l1 - 1, l2 - 1, 1, i));
  }
  
  for (auto&amp;&amp; [ql, qr, f, idx] : query) {
    if (ql &gt; qr) swap(ql, qr);
  }
  
  block = sqrt(n);
  num = (n + block - 1) / block;
  
  for (int i = 1; i &lt;= num; i++) {
    l[i] = (i - 1) * block + 1;
    r[i] = i * block;
    
    for (int j = l[i]; j &lt;= r[i]; j++)
      pos[j] = i;
  }
  
  sort(query.begin(), query.end(), [&amp;] (auto a, auto b) -&gt; bool {
    if (pos[get&lt;0&gt;(a)] != pos[get&lt;0&gt;(b)])
      return pos[get&lt;0&gt;(a)] &lt; pos[get&lt;0&gt;(b)];
    if (pos[get&lt;0&gt;(a)] &amp; 1)
      return get&lt;1&gt;(a) &lt; get&lt;1&gt;(b);
    return get&lt;1&gt;(a) &gt; get&lt;1&gt;(b);
  });
  
  int L = 0, R = 0;
  for (auto&amp;&amp; [ql, qr, f, idx] : query) {
    while (L &lt; ql) UpdL(a[++L], 1);
    while (L &gt; ql) UpdL(a[L--], -1);
    while (R &lt; qr) UpdR(a[++R], 1);
    while (R &gt; qr) UpdR(a[R--], -1);
    
    ans[idx] += f * cur;
  }
  
  for (int i = 1; i &lt;= q; i++) {
    cout &lt;&lt; ans[i] &lt;&lt; &quot;\n&quot;;
  }
  
  return 0;
}</code></pre></div><p style="text-align:right"><a href="https://unleafy.cn/posts/solution/mo-algorithm#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/solution/mo-algorithm</link><guid isPermaLink="true">https://unleafy.cn/posts/solution/mo-algorithm</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Wed, 18 Mar 2026 08:55:09 GMT</pubDate></item><item><title><![CDATA[[置顶] 博客公告]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/others/announcement">https://unleafy.cn/posts/others/announcement</a></blockquote><div><h1 id="2026-2-23">2026-2-23</h1><p> 🎉购买云服务器，正式开始网站搭建。</p><h1 id="2026-2-25">2026-2-25</h1><p>🎉 Blog 前端和后端搭建完成。</p>
<h1 id="2026-2-26">2026-2-26</h1><p>🔺尝试配置 Github OAuth 失败 qwq。</p><h1 id="2026-3-5">2026-3-5</h1><p>🎉 旧 hexo 博客文章搬运完成！</p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/others/announcement#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/others/announcement</link><guid isPermaLink="true">https://unleafy.cn/posts/others/announcement</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 13:10:57 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day30]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day30">https://unleafy.cn/posts/record/2025-summer-Day30</a></blockquote><div><p><strong>Content</strong>：模拟赛
<strong>Date</strong>：2025.8.15</p><h1 id="problem-a-httpszhengruioicomproblem3268cid1960">Problem-A <a href="https://zhengruioi.com/problem/3268?cid=1960">图书配对</a></h1><h2 id="">题目描述</h2><p>给定 $n$ 本图书，定义 $merge(a<em>{i},a</em>{j})$ 表示 $a<em>i$, $a</em>{j}$ 直接拼接得到的结果，求满足 $merge(a<em>{i},a</em>{j}) \mid 9$ 的个数（一个数只能取一次）。</p><h2 id="">解题思路</h2><p>我们可以运用小学奥数的知识，如果一个数能被 $9$ 整除，当且仅当这个数的各个数位之和能被 $9$ 整除，知道这个之后就是一个很经典的问题了。</p><p>提交记录：<a href="https://zhengruioi.com/submission/936081">Link</a></p><h1 id="problem-b-httpszhengruioicomproblem3269cid1960">Problem-B <a href="https://zhengruioi.com/problem/3269?cid=1960">菜品搭配</a></h1><h2 id="">题目描述</h2><p>给定数组 $a$，求 $\displaystyle \sum<em>{i=0}^{n-2} \sum</em>{j=i+1}^{n-1} \sum<em>{k=j+1}^{n} (a</em>{i} \oplus a<em>{j}) \times (a</em>{j} \oplus a_{k})$。</p><h2 id="">解题思路</h2><p>由于是二进制意义下的异或运算，很容易想到拆位，计算每一位的贡献。而且求的是三元组 $(i,j,k)$，所以我们枚举 $j$ 的位置，考虑 $j$ 左右两边的贡献。</p><p>对于异或 <strong>相同为 0，不同为 1</strong> 的运算法则，我们可以发现，$i,k$ 对于 $j$ 的贡献是左右两边与 $j$ 不相同的位置的个数的乘积，所以我们对于每一个位置计算贡献即可。</p><p>涂胶记录：<a href="https://zhengruioi.com/submission/937158">Link</a></p><h1 id="problem-c-httpszhengruioicomproblem3270cid1960">Problem-C <a href="https://zhengruioi.com/problem/3270?cid=1960">课程推荐</a></h1><h2 id="">题目大意</h2><p>给你一个数组 $a$，其中 $a<em>i$ 表示到达 $i$ 这个位置后你可以到达 $[i+1,a</em>{i}]$ 中的任何一个位置，求 $\forall i,j$ 互相可达的最小步数之和。</p><h2 id="">解题思路</h2><p>赛时没有想到是 $dp$，直接写了个 Floyd (20pts) + 部分分(10pts)。</p><p>正解是定义 $d_{i,j}$ 表示从 $i$ 到 $j$ 需要的最少步数。容易发现：</p><ul><li>若 $j \in [i + 1, a<em>{i}]$，则 $d</em>{i,j}=1$。</li><li>若 $j &gt; a<em>{i}$，则 $d</em>{i,j} = d<em>{k,j} + 1$，其中 $k$ 表示 $\max</em>{t\in[i + 1, a_{i}]} a_t$ 所在的位置。</li></ul><p>然后记 $\displaystyle f<em>{i} = \sum</em>{j=i}^{n} d<em>{i,j}$，则 $f</em>{i} = f<em>{p} + (n - i) - (a</em>{i} - p)$。答案即为 $\displaystyle \sum<em>{i=0}^{n} f</em>{i}$。</p><hr/><p>终于结束啦喵~</p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day30#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day30</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day30</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 13:00:01 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day29]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day29">https://unleafy.cn/posts/record/2025-summer-Day29</a></blockquote><div><p><strong>Content</strong>：杂题
<strong>Date</strong>：2025.8.14</p>
<h1 id="codeforces-1870ehttpswwwluogucomcnproblemcf1870e-another-mex-problem"><a href="https://www.luogu.com.cn/problem/CF1870E">CodeForces-1870E</a> Another MEX Problem</h1><h2 id="">题目大意</h2><p>给你一个数组 $a$，你可以选择任意互不相交的子数组，先计算每一个子数组的 MEX 值，然后将这些 MEX 值的异或和作为这个方案的价值，求你可以获得的最大价值。</p><h2 id="">解题思路</h2><p>首先我们肯定可以暴力 DP，定义 $dp_{i,j}$ 表示前 $i$ 个数能否凑出价值为 $j$ 的方案，转移显然是 $O(n^3)$ 的。</p><p>考虑如何优化。由于 MEX 的性质，可以发现从 $i$ 往后的一段区间内 MEX 值是单调不降的，所以其中一定有大量相等的段。这些段本质是一样的，所以我们只需要考虑那些前后不同的位置即可，这些符合条件的转移区间至多有 $2n$ 个，所以我们就得到了复杂度为 $O(n^2)$ 得做法。</p><p>提交记录：<a href="https://vjudge.net/solution/62866466">Link</a></p><h1 id="luogu-p12426httpswwwluogucomcnproblemp12426-boi-acroym"><a href="https://www.luogu.com.cn/problem/P12426">Luogu-P12426</a> BOI acroym</h1><h2 id="">题目大意</h2><p>已知一个由 B, O, I 组成得字符串每个区间得众数得出现次数，且 B 为全局众数，求原串种 B 出现的位置。</p><h2 id="">解题思路</h2><p>首先我们可以找到第一个和最后一个出现的 B 的位置。</p><ul><li>左侧最后一个满足 $m<em>{1,n} = m</em>{l,n}$ 的位置 $l$。</li><li>右侧最后一个满足 $m<em>{1,n} = m</em>{1,r}$ 的位置 $r$。</li></ul><p>然后我们考虑根据已知信息推出区间 $(l,r)$ 内的 B 的位置。主要有以下三种情况 ($cur$ 表示当前已知的 B 的数量)：</p><ol start="1"><li>若 $m<em>{l,i - 1} = cur$ 且 $m</em>{l + 1, i - 1} = cur - 1$ 且 $m<em>{l,i} = m</em>{l,i - 1} + 1$，则位置 $i$ 为 B。</li><li>若 $m<em>{i,r} = m</em>{l,r} - cur$ 且 $m<em>{i, r - 1} = m</em>{l,r} - cur - 1$ 且 $m<em>{i,r} = m</em>{i + 1,r} + 1$，则位置 $i$ 为 B。</li><li>若 $m<em>{l + 1,i} = m</em>{l,i-1}$ 且 $m<em>{i,r} = m</em>{i + 1,r - 1}$，则位置 $i$ 为 B。</li></ol><p>提交记录：<a href="https://www.luogu.com.cn/record/230905777">Link</a></p><h1 id="luogu-p8386httpswwwluogucomcnproblemp8386-pa-2021-od-deski-do-deski"><a href="https://www.luogu.com.cn/problem/P8386">Luogu-P8386</a> [PA 2021] Od deski do deski</h1><h2 id="">题目描述</h2><p>给定 $n$，$m$，求满足以下限制的长度为 $n$ 的序列数目：</p><ol start="1"><li>每个元素在 $[1,m]$ 之间；</li><li>一次操作定义为删除一个长度至少为 $2$ 且区间两端相等的区间，该序列需要在若干次操作内被删空。</li></ol><p>答案对 $10^9+7$ 取模。</p><h2 id="">解题思路</h2><p>可以发现最后的答案一定是由 $a \dots\dots\dots a$ 这样的字符串拼接成的。所以我们定义 $f_{i,j,0 / 1}$ 表示前 $i$ 个位置包含 $j$ 个不同的数且符合/不符合条件的方案数，转移显然。</p><p>$$
\begin{align}
f<em>{i,j,1} \times j &amp;\to f</em>{i+1,j,1} \
f<em>{i,j,1} \times (m - j) &amp;\to f</em>{i+1,j + 1,0} \
f<em>{i,j,0} \times j &amp;\to f</em>{i+1,j,1} \
f<em>{i,j,0} \times (m - j) &amp;\to f</em>{i+1,j,0}
\end{align}
$$</p><p>答案即为 $\displaystyle \sum<em>{i=0}^{n} f</em>{n,i,1}$，注意取模。</p><p>提交记录：<a href="https://www.luogu.com.cn/record/230890956">Link</a></p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day29#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day29</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day29</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:59:27 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day28]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day28">https://unleafy.cn/posts/record/2025-summer-Day28</a></blockquote><div><p><strong>Content</strong>：网络流进阶
<strong>Date</strong>：2025.8.13</p>
<p><del>昨天太累了，没写，就今天补上吧</del></p><h1 id="">课堂内容</h1><h2 id="">上下界网络流</h2><p>OI Wiki：<a href="https://oi-wiki.org/graph/flow/bound/">Link</a></p><p>很可惜，没学懂，但是大概意思应该是用差分的思想将原来的上下界网络流转换为网络最大流。<del>没学过真的听不懂啊……</del></p><h1 id="">题目</h1><h2 id="-62-">模拟题-6.2 异或</h2><h3 id="">题目大意</h3><p>给你一个长度为 $n$ 的数组 $a$，问你满足 $(a<em>{i} \oplus a</em>{j}) &lt; (a<em>{j} \oplus a</em>{k})$ 的三元组 $(i,j,k)$ 有多少个。</p><h3 id="">解题思路</h3><p>由于异或 <strong>相同为 0，不同为 1</strong> 的运算规则，可以发现要满足上述的不等式 $(a<em>{i} \oplus a</em>{j}) &lt; (a<em>{j} \oplus a</em>{k})$ 只与这三个数第一个在二进制下不同的位置 $p$ 有关。具体来说：</p><ul><li>若 $a<em>{k,p} = 1$，则 $a</em>{i,p} = a_{j,p} = 0$;</li><li>若 $a<em>{k,p} = 0$，则 $a</em>{i,p} = a_{j,p} = 1$。</li></ul><p>发现其实和二进制串的匹配有关，可以用 0/1 字典树解决，在字典树上计算贡献。</p><h3 id="code">Code</h3><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;bits/stdc++.h&gt;

using namespace std;

constexpr int N = 5e5 + 5;
constexpr int Z = 2;
long long ans = 0;
int n, arr[N], mark[50][2];

struct Trie {
    int tr[N * 32][Z], cnt = 1;
    long long pass[N * 32][2];
    
    Trie() {
        // memset(tr, -1, sizeof(tr));
        // memset(pass, 0, sizeof(pass));
        tr[0][0] = tr[0][1] = -1;
        cnt = 1;
    }
    
    void Insert(int number) {
        int pos = 0;
        pass[pos][1]++;
        
        for (int i = 30; i &gt;= 0; i--) {
            int ch = (number &gt;&gt; i) &amp; 1;
            
            if (tr[pos][ch] == -1) {
                tr[cnt][0] = tr[cnt][1] = -1;
                pass[cnt][0] = pass[cnt][1] = 0;
                tr[pos][ch] = cnt++;
            }
            
            pos = tr[pos][ch];
            pass[pos][0] += mark[i][ch];
            pass[pos][1]++;
            mark[i][ch]++;
            
            // cout &lt;&lt; &quot;Insert: &quot; &lt;&lt; pos &lt;&lt; &#x27; &#x27; &lt;&lt; pass[pos][0] &lt;&lt; &#x27; &#x27; &lt;&lt; pass[pos][1] &lt;&lt; &#x27; &#x27; &lt;&lt; mark[i][ch] &lt;&lt; &#x27;\n&#x27;;
        }
    }
    
    long long Query(int number) {
        int pos = 0;
        long long retval = 0;
        
        for (int i = 30; i &gt;= 0; i--) {
            int ch = (number &gt;&gt; i) &amp; 1;
            int rev = 1 - ch;
            
            // ! 
            if (tr[pos][rev] != -1) {
                // cout &lt;&lt; pass[tr[pos][rev]][0] &lt;&lt; &#x27; &#x27; &lt;&lt; pass[tr[pos][rev]][1] &lt;&lt; &quot; &quot; &lt;&lt; mark[i][rev] &lt;&lt; &quot;\n&quot;;
                
                retval += 1ll * pass[tr[pos][rev]][1] * mark[i][rev] - pass[tr[pos][rev]][1] - pass[tr[pos][rev]][0];
            }
                
            pos = tr[pos][ch];
            if (pos == -1) {
                break;
            }
        }
        
        return retval;
    }
} t;

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    
    memset(mark, 0, sizeof(mark));
    
    cin &gt;&gt; n;
    
    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; arr[i];
    }
    
    for (int i = 1; i &lt;= n; i++) {
        ans += t.Query(arr[i]);
        
        // cout &lt;&lt; &quot;\n&quot;;
        
        t.Insert(arr[i]);
    }
    
    cout &lt;&lt; ans &lt;&lt; &#x27;\n&#x27;;
    
    return 0;
}</code></pre></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day28#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day28</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day28</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:58:57 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day27]]></title><description><![CDATA[<link rel="preload" as="image" href="https://backend.unleafy.cn/api/v2/objects/icon/67ipf4ivnck7ja1w61.jpg"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day27">https://unleafy.cn/posts/record/2025-summer-Day27</a></blockquote><div><p><del>昨天忘记传了喵</del>~</p><h1 id="">课堂内容</h1><h2 id="">二分图匹配</h2><h3 id="hall-">Hall 定理</h3><blockquote><p>假设 $G = (X,Y,E)$ 是一个二分图，且 $|X| \le |Y|$。对于 $W \subseteq X$，记 $N_{G}(W)$ 表示在图 $G$ 中所有与集合 $W$ 中的点相邻的点的集合。那么 $X$ 的完美匹配存在，当且仅当 $|W| \le |N_G(W)|$ 对于所有 $W \subseteq X$ 存在。</p></blockquote>
<h3 id="">匈牙利算法</h3><p>不断搜索增广路，每次考虑一条增广路，看看把一个匹配拆了能否增加一个新的匹配。</p><p>模板代码：<a href="https://www.luogu.com.cn/record/173238737">Link</a></p><h2 id="">网络最大流算法</h2><h3 id="dinic">Dinic</h3><p>Dinic 是求解网络最大流的其中一个算法，也是最常用的算法。其核心思想为：每次用 BFS 寻找增广路，然后用 DFS 统计增广路上的最大流量。但是这样会有一些问题，如果给定的网络是如下情况的话：</p><p><img src="https://backend.unleafy.cn/api/v2/objects/icon/67ipf4ivnck7ja1w61.jpg" alt="img" height="1241" width="1906"/></p><p>如果直接按照上面说的方法走，第一次增广路可能是 $1 \to 2 \to 3 \to 4$，但实际上最优的是 $1 \to 2 \to 4$ 和 $1 \to 3 \to 4$。所以为了解决这个问题，我们需要对原图的所有边建立反向边 $(v,u,0)$。如果我们选择了这条路，则将其反向边的流量减去我们现在流过这条边的流量，这样可以做到类似反悔贪心的效果。</p><p>模版代码：<a href="https://www.luogu.com.cn/record/230404491">Link</a></p><h2 id="">费用流</h2><h3 id="-min-cost-max-flow-mcmf">最小费用最大流 (Min Cost Max Flow, MCMF)</h3><p>和原来的网络图相比，每一条边新加了一个限制 $c$ 表示流过 $1$ 个单位的流量需要花费 $c$ 的代价。</p><p>我们在原来的 Dinic 算法的基础上进行修改，每次沿着最短路径进行增广。由于有带负权的反向边存在，我们不能使用 Dijkstra，而是要使用 SPFA。</p><p>模板代码：<a href="https://www.luogu.com.cn/record/230410688">Link</a></p><h1 id="">题目</h1><h2 id="luogu-p3254httpswwwluogucomcnproblemp3254-"><a href="https://www.luogu.com.cn/problem/P3254">Luogu-P3254</a> 圆桌问题</h2><h3 id="">题目描述</h3><p>一共有 $m$ 个不同国家的代表团来参加国际会议，第 $i$ 个代表团共有 $r<em>i$ 个代表参加会议，会议场地内共有 $n$ 张桌子，每张桌子最多可以做 $c</em>{i}$ 个人。规定一张桌子上不能坐超过一个相同代表团的人。问是否存在一种合法的分配方案。</p><h3 id="">解题思路</h3><p>因为一张桌子最多可以做一个同一个代表团的代表，所以我们将每个代表团向每个桌子建一条流量为 1 的边。然后我们把超级源点和每个代表团之间链接一条流量为 $r<em>{i}$ 的边，再把所有桌子和超级汇点连接一条流量为 $c</em>{i}$ 的边，然后跑最大流即可。</p><p>提交记录：<a href="https://www.luogu.com.cn/record/list?pid=P3254&amp;user=1023191">Link</a></p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day27#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day27</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day27</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:58:10 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day26]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-suimmer-Day26">https://unleafy.cn/posts/record/2025-suimmer-Day26</a></blockquote><div><h1 id="">课堂内容</h1><h2 id="">差分约束</h2><h3 id="problem">Problem</h3><p>给定一个包含 $n$ 个不等式的不等式组，要求求出这个不等式组的任意一组解，或者判断不等式组无解。</p><h3 id="solution">Solution</h3><p>对于给定的这个问题，整理一下，发现要求解：</p><p>$$
\begin{cases}
x<em>1 - x</em>{2} &gt; c<em>{1} \
x</em>{2} - x<em>{3} &gt; c</em>{2} \
x<em>{3} - x</em>{4} &gt; c<em>{3} \
\dots \
x</em>{n} - x<em>{1} &gt; c</em>{n}
\end{cases} \Rightarrow \begin{cases}
x<em>1 &gt; x</em>{2} + c<em>{1} \
x</em>{2} &gt; x<em>{3} + c</em>{2} \
x<em>{3} &gt; x</em>{4} + c<em>{3} \
\dots \
x</em>{n} &gt; x<em>{1} + c</em>{n}
\end{cases}
$$</p><p>容易发现这些不等式的形式和 <em>Dijkstra</em> 中三角不等式 $dis<em>{u} &gt; dis</em>{v} + w$ 类似。更进一步地，我们发现如果将不等式组中的不等式转化成边 $(x<em>{i},x</em>{i+1},c_{i})$ 的话，其实的解就是 $dis_i$。如果原问题无解的话，相当于在图上存在负环。至此，问题解决。</p><p>需要注意的是，因为要判断是否存在负环，所以不能使用 <strong>Dijkstra</strong>，而是要使用 <strong>SPFA</strong> 解决此问题。</p><h2 id="lca">LCA（最近公共祖先）</h2><h3 id="problem">Problem</h3><p>给点一棵树，每次询问  $(u,v)$ 的最近公共祖先 $LCA(u,v)$。</p><h3 id="solution">Solution</h3><h4 id="">倍增</h4><p>首先我们处理出所有点的直接父亲是谁，然后利用倍增的思路，处理出这个点向上跳一条长度为 $2^k$ 的链后它的父亲是谁。</p><p>具体地，我们需要得到一个数组 $jump<span>[u][k]</span>$ 表示节点 $u$ 向上跳 $2^k$ 步后他的父亲是谁，即 $\displaystyle jump<span>[u][k]</span> = jump<span>[jump[u][k - 1]</span>][k-1]$，先向上跳 $2^{k-1}$ 步，再跳 $2^{k-1}$ 步，就得到了我们想要的答案。</p><p>查询的时候只需要暴力向上跳即可，复杂度 $O(m \log n)$。</p><h4 id="">树剖</h4><p>我们先对原树做重链剖分，然后查询的时候每次跳一条重链，直到两条重链的链头的父亲相同。复杂度 $O(m \log n)$。</p><h4 id="st-">ST 表</h4><p>我们在原树上处理出欧拉序，由于原树上的任意一颗子树在欧拉序上对应了一个连续的区间，所以问题就转化为了查询这个区间内深度最小的节点的 <em>RMQ</em> 问题，可以用 <em>ST</em> 表解决，复杂度为预处理 $O(n \log n)$，查询 $O(1)$。</p><h4 id="tarjan">Tarjan（离线）</h4><p>我们将所有询问离线下来，然后在 Tarjan 的时候判断与当前这个点有关的所有查询中，是否存在另一个点已经被访问了的查询，如果存在，就更新即可。期间用并查集维护 LCA，复杂度 $O(\alpha(m + n,n) + n)$。</p><h1 id="">例题</h1><h2 id="arc084bhttpsatcoderjpcontestsmath-and-algorithmtasksarc084blangen-small-multiple"><a href="https://atcoder.jp/contests/math-and-algorithm/tasks/arc084_b?lang=en">ARC084B</a> Small Multiple</h2><h3 id="problem">Problem</h3><p>给定一个数字 $k$，要求找到最小的 $k$ 的倍数，使得所有数位上的数字和最小。</p><h3 id="solution">Solution</h3><p>我们不难发现，一个数 $x \mid k$ 等价于如果我们用 $c<em>{i}$ 表示 $x$ 的每一个数位上的数，则 $\forall i,c</em>{i} \times 10^i \bmod k = 0$。所以我们可以对于每一个数位考虑。不难发现，每一次操作就是 $\times 10$ 或者 $+1$，所以我们可以 0/1 BFS。</p><p>提交记录：<a href="https://atcoder.jp/contests/math-and-algorithm/submissions/68406044">Link</a></p><h2 id="luogu-p1993httpswwwluogucomcnproblemp1993--k-"><a href="https://www.luogu.com.cn/problem/P1993">Luogu-P1993</a> 小 K 的农场</h2><h3 id="problem">Problem</h3><p>给定共 $m$ 个以下形式的不等式组：</p><ol start="1"><li>$a-b \ge c$；</li><li>$a-b \le c$；</li><li>$a = b$；</li></ol><p>求其中任意一组解。</p><h3 id="solution">Solution</h3><p>我们将题目中的不等式组变换一下：</p><ol start="1"><li>$a-b \ge c \to b \le a + c$；</li><li>$a-b \le c \to a \le b + c$；</li><li>$a = b \to a \le b$ 和 $b \le a$；</li></ol><p>所以我们按照差分约束的方式建图即可。</p><p>提交记录：<a href="https://www.luogu.com.cn/record/list?pid=P1993&amp;user=1023191">Link</a></p><h2 id="codeforces-776dhttpswwwcodeforcescomproblemsetproblem776d-the-door-problem"><a href="https://www.codeforces.com/problemset/problem/776/D">CodeForces-776D</a> The Door Problem</h2><h3 id="problem">Problem</h3><p>给你 $n$ 扇门和 $m$ 个开关，每个开关控制 $k$ 扇不同的门，一扇门最多被两个开关控制。初始时每扇门要么打开，要么关闭。变换一个开关的状态会使门的状态也发生变化，问你存不存在一种方案，使得所有门均开启。</p><h3 id="solution">Solution</h3><p>不难发现，最初打开的门所对应的开关要么同时打开，要么同时关闭，所以我们可以用并查集维护每个开关的状态。若果这个门最初是关闭的，则将 $(x,y)$ 和 $(x+m,y+m)$ 合并，否则将 $(x+m,y)$ 和 $(y + m,x)$ 合并。最后检查每个开关的状态是否合法即可。</p><p>提交记录：<a href="https://vjudge.net/solution/62765202">Link</a></p><h2 id="-52--k-">模拟题-5.2 第 K 大查询</h2><h3 id="problem">Problem</h3><p>给定一个 $1 \backsim n$ 的排列 $a$，想知道所有满足 $r-l+1\ge k$ 的区间 $[l,r]$ 内第 K 大的数的和是多少。</p><h3 id="solution">Solution</h3><p>我们将原来的排列按照从小到大排序，发现一个数在区间 $[l,r]$ 是第 $k$ 大当且仅当区间中包含 $k-1$ 个比它大的数。所以我们可以用双向链表维护这个思路。每次暴力像两边扩展，然后计算贡献，最后在双向链表中删除这个数。时间复杂度 $O(nk)$。</p><h3 id="code">Code</h3><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;algorithm&gt;
#include &lt;iostream&gt;

using std::cin;
using std::cout;

constexpr int N = 5e5 + 5;
int n, k, arr[N], index[N];
long long answer = 0;

struct Node {
    int left, right;
} link[N];

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    
    cin &gt;&gt; n &gt;&gt; k;
    
    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; arr[i];
        index[i] = i;
        link[i] = (Node) {i - 1, i + 1};
    }
    
    std::sort(index + 1, index + n + 1, [&amp;] (int a, int b) { return arr[a] &lt; arr[b]; });
    
    for (int i = 1; i &lt;= n; i++) {
        int _index = index[i];
        int left = _index, right = _index;
        int rank = 1;
        
        while (link[left].left != 0 &amp;&amp; rank &lt; k) {
            rank++;
            left = link[left].left;
        }
        
        while (link[right].right != n + 1 &amp;&amp; rank &lt; k) {
            rank++;
            right = link[right].right;
        }
        
        // cout &lt;&lt; left &lt;&lt; &#x27; &#x27; &lt;&lt; right &lt;&lt; &quot;\n&quot;;
        
        if (rank == k) {
            while (left != link[_index].right &amp;&amp; right != n + 1) {
                answer += 1ll * (left - link[left].left) * (link[right].right - right) * arr[_index];
                left = link[left].right;
                right = link[right].right;
            }
        }
        
        link[link[_index].left].right = link[_index].right;
        link[link[_index].right].left = link[_index].left;
    }
    
    cout &lt;&lt; answer &lt;&lt; &#x27;\n&#x27;;
    
    return 0;
}</code></pre></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-suimmer-Day26#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-suimmer-Day26</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-suimmer-Day26</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:53:54 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day25]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day25">https://unleafy.cn/posts/record/2025-summer-Day25</a></blockquote><div><h1 id="">课堂内容</h1><h2 id="">图论基础知识</h2><ul><li>图的定义：由点集 $V$ 和边集 $E$ 组成，记作图 $G (V, E)$。</li><li>阶：图的点数，记作 $|G|$。</li><li>相邻：称图中的两个点 <em>相邻</em>，当且仅当 $(i,v) \in E$。</li><li>连通：无向图中若存在 $v_0 = u, v_k = v$ 的路径则称 $u, v$ 连通。</li><li>弱连通：若有向图变为无向图后 $u, v$ 连通，则称 $u, v$ 弱连通。</li><li>连通图：无向图中任意两点连通的图称作连通图。</li><li>弱连通图：有向图中任意两点弱连通的图称作弱连通图。</li><li>简单图：不存在重边和自环的图。</li><li>稀疏图/稠密图：$|E| \ll |V|^2$ 的图称作稀疏图，$|E|$ 接近 $|V|^2$ 的图称作稠密图。</li></ul><h2 id="">拓扑排序</h2><h3 id="problem">Problem</h3><p>给定一个有向无环图（DAG），要求求出一个序列 $p$，其中 $u$ 在排列 $p$ 中的位置记作 $q_u$，则这个序列满足满足：</p><ul><li>$\forall \ u \to v \in E$，$q_u &lt; q_v$。</li></ul><h3 id="solution">Solution</h3><p>每次从 DAG 中选择一个入度为 0 的点，将这个点加入序列 $p$ 中，然后删掉这个点和这个点在图中的所有出边，重复这个过程。</p><h3 id="proof">Proof</h3><p>显然对于任意的 $u \to v \in E$，点 $u$ 总是在 $v$ 之前被加入序列 $p$，所以最后得到的学列一定是合法的。复杂度为 $O (n+m)$。</p><h3 id="extend">Extend</h3><p>可以用拓扑排序判断一个有向图是否存在环。</p><h2 id="">最短路算法</h2><h3 id="problem-1">Problem 1</h3><p>给定一张图和一个源点 $s$，求图上所有点到源点 $s$ 的最短距离 $D_u$。</p><h3 id="solution">Solution</h3><h4 id="bellman-ford">Bellman-Ford</h4><p>松弛操作：称一次松弛操作为对于每一条边 $(u, v, w)$，用 $dis_u + w \to dis_u$。
这样进行松弛操作 $n-1$ 轮就可以得到 $D_u$。复杂度为 $O (nm)$。</p><p><strong><em>Extend</em></strong>: Bellman-Ford 算法也可以用来判断图中是否存在负环。如果进行了 $n-1$ 轮松弛操作后依然存在可以被更新的 $dis_u$，则图中一定存在负环。因为在这个操作中负环被视为无穷小而被无限更新下去。</p><h4 id="spfa">SPFA</h4><p><del>关于 SPFA，它死了。</del></p><p>松弛点 $u$ 时若存在可以用 $(u, v, w)$ 更新的 $dis_v$ 且 $v$ 不在队列中，则将 $v$ 压入队列，重复这个过程知道队列为空。复杂度 $O(nm)$。</p><p><strong><em>Extend</em></strong>：和 bellman-ford 一样，如果存在点被压入队列超过 $n-1$ 次，则图中存在负环。</p><h4 id="dijkstra">Dijkstra</h4><p>扩展：对于一个点 $u$，称扩展点 $u$ 表示用 $u$ 的所有出边 $(u, v, w)$ 更新 $dis_v$。</p><p>每次选择未被扩展的 $dis_u$ 最小的点 $u$ 进行扩展，可以用 <em>优先队列</em> 将复杂度优化到 $O(m \log m)$，但只能处理 <strong>非负权图</strong>。</p><h3 id="problem-2">Problem 2</h3><p>对于一个给定的图，求图中任意两点的最短距离。</p><h3 id="solution">Solution</h3><h4 id="johnson">Johnson</h4><p>从超级源点向所有点连一条 $(S, u, 0)$ 的边，跑一边 <em>Bellman-Ford/SPFA</em> 得到最短路 $h<em>u$。然后改造边为 $(u, v, w + h_u - h</em>{v})$。然后再在新图上跑一边 <em>Dijkstra</em>（新图为非负权图），复杂度为 $O (nm \log m)$。</p><h4 id="floyd">Floyd</h4><p>初始化所有 $dis<em>{u, u} = 0;\forall (u,v,w) \in E,dis</em>{u,v} = \min{w}$，然后在这个数组上进行如下更新：</p><p>$$
dis<em>{i,j} = \min</em>{k} (dis_{i,k} + dis{k,j})
$$</p><p>实际上就是一个动态规划，复杂度为 $O (n^3)$。</p><h2 id="">最短路树</h2><p>在单源最短路中，记录每个点最后被更新的前驱 $pre_u$，以这个建边得到的树就是最短路树。</p><h2 id="">删边最短路</h2><h3 id="problem">Problem</h3><p>给定一个正权无向图，求删除每一条边后从 $1$ 到 $n$ 的最短路。</p><h3 id="solution">Solution</h3><p>我们建立一个从 $1$ 出发的最短路树 $T_1$，和一个到达 $n$ 的最短路树 $T_2$。容易发现若删除的边 $(u, v)$ 不在原最短路上，则删除 $(u,v)$ 不会影响答案。</p><p>所以我们不妨设原最短路为 $1 \leadsto i \to j \leadsto n$，找到所有满足 $u$ 不在 $T_2$ 的 $i$ 的子树内，$v$ 不在 $T_1$ 的 $j$ 的子树内的所有边 $(u, v, w)$。用 $dis(u,LCA(T_1,u,n)) + w(u,v) + dis(v,LCA(T_2,v,1))$ 更新答案。复杂度可以用数据结构优化到 $O(m \log m)$。</p><h2 id="">无向图连通性</h2><h3 id="">定义</h3><ul><li>割边：删去后使得连通分量增加的边。</li><li>割点：删去后使得连通分量增加的点</li><li>点双连通图：不存在割点的无向连通图。</li><li>边双连通图：不存在割边的无向连通图。</li><li>点双连通分量：极大点双连通子图，即 <em>V-BCC</em>。</li><li>边双连通分量：极大边双连通子图，即 <em>E-BCC</em>。</li></ul><h3 id="">性质</h3><h4 id="">点双连通分量</h4><ul><li>若两个点双连通分量有交点，则这个交点有且仅有一个，且为割点。</li><li>一个点是割点当且仅当它属于超过一个边双连通分量。</li><li>一条边仅属于一个点双连通分量。</li><li>由一条边直接连接的两个点 <em>点双连通</em>。</li><li>点双内任意一个点 $u$ 均存在经过这个点的简单环。</li><li>点双内任意一个点对 $(u,v)$，均存在包含这个点对的简单环。</li></ul><h4 id="">边双连通分量</h4><ul><li>两个点之间任意一条 <em>迹</em> 上的所有割边就是这两个点的必经边。</li><li>若 $x$ 和 $y$ 边双连通，$y$ 和 $z$ 边双连通，则 $x$ 和 $z$ 边双连通（传递性）。</li><li>$u$ 和 $v$ 之间边双连通当且仅当这两个点之间的必经边集合为空。</li><li>对于一个边双连通分量中的任意一条边 $(u,v)$，均存在经过这条边的回路。</li><li>边双内任意一点均存在经过这个点的回路。</li><li>边双内任意一组点对 $(u,v)$ 均存在包含这个点对的回路。</li></ul><h4 id="">求解</h4><ul><li>点双连通分量：<a href="https://www.luogu.com.cn/record/183873350">Link</a></li><li>边双连通分量：<a href="https://www.luogu.com.cn/record/167131116">Link</a></li></ul><h2 id="">有向图连通性</h2><h3 id="">定义</h3><ul><li>强连通：一个点对 $(u,v)$ 互相可达。</li><li>强连通图：图中任意两点互相可达的有向连通图。</li><li>强连通分量：极大的强连通子图，即 <em>SCC</em>。</li></ul><h3 id="">性质</h3><ul><li>若 $dfn_v &lt; dfn_u$，则 $u$ 可达 $v$ 当且仅当 $v$ 可达 $u$。</li><li>若 $u,v$ 强连通，则 $u,v$ 在 dfs 树上路径的所有点弱连通。</li><li>强连通分量在 dfs 树上弱连通。</li></ul><h3 id="">求解</h3><ul><li>强连通分量（Tarjan）：<a href="https://www.luogu.com.cn/record/172778304">Link</a></li></ul><h2 id="">最小生成树</h2><h3 id="">定义</h3><ul><li>生成树：对于连通图而言，生成树是原图一个树形结构的生成子图。</li><li>生成森林：每一个连通分量的生成树组成的子图。</li></ul><h3 id="">求解最小生成树</h3><h4 id="kruskal">Kruskal</h4><p>首先对原图的边按照边的权值从小到大排序，然后枚举每一条边，如果这条边连接的两个点现在并不连通，则将这一条边加入最小生成树。连通性可以用并查集维护，复杂度为 $O(m \log m)$。</p><p>实现：<a href="https://www.luogu.com.cn/record/230241875">Link</a></p><h4 id="prim">Prim</h4><p>类似 Dijkstra，每次选择一个现在在最小生成树上的点 $u \in S$（$S$ 为当前已经加入最小生成树的点集），将满足 $(u,v,w) \in E,v \not\in S$ 且 $w$ 最小的边加入最小生成树。用优先队列可以把复杂度优化到 $O((n+m) \log n)$。</p><p>实现：<a href="https://www.luogu.com.cn/record/173313541">Link</a></p><h1 id="">例题</h1><h2 id="luogu-p1967httpswwwluogucomcnproblemp1967-noip-2013--"><a href="https://www.luogu.com.cn/problem/P1967">Luogu-P1967</a> [NOIP 2013 提高组] 货车运输</h2><h3 id="">题目大意</h3><p>给定一张由 $n$ 个点 $m$ 条双向边组成的无向带权图，有 $q$ 次询问，每次询问 $u, v$ 之间的最大瓶颈路。</p><h3 id="">思路</h3><p>首先如果对于每次询问都单独跑一边 <em>Dijkstra</em> 的复杂度是 $O (n m \log m)$ 的，显然不可接受。</p><p>我们发现由于题目要求的是瓶颈路，所以很多边都是用不到的。更进一步的，我们发现只有原图中 <strong>最大生成树</strong> 上的边才是有用的。所以我们可以对于原图的最大生成树重新建边，然后这个问题就转化为了树上问题。</p><p>转化为树上问题后，$u, v$ 的路径很显然可以分为 $u \to LCA (u, v)$ 和 $v \to LCA (u, v)$，所以就转化为了求树上两个点之间路径上的最小权值。可以用倍增求 $LCA$，复杂度为 $O(n \log n)$。</p><p>提交记录：<a href="https://www.luogu.com.cn/record/230047503">Link</a></p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day25#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day25</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day25</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:53:01 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day24]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day24">https://unleafy.cn/posts/record/2025-summer-Day24</a></blockquote><div><h1 id="problem-a-ieee-754httpszhengruioicomproblem3281cid1952">Problem-A <a href="https://zhengruioi.com/problem/3281?cid=1952">IEEE 754</a></h1><h2 id="">题目描述</h2><p>题目背景告诉你浮点数表示法(IEEE 754 标准)，要求你求 $5^n$，其中 $n &lt; 1024$。</p><h2 id="">思路</h2><p>赛场上是直接写的高精度，但是赛后看过题解发现有更简单的做法。</p><p>首先用 <em>Python</em> 计算发现，$5^1023$ 大约有 800 位，而 <em>double</em> 类型的存储精度有 308 位，所以可以用 <em>double</em> 类型计算，但是要计算的是 $5^(-n)$，因为是浮点数。</p><p>提交记录：<a href="https://zhengruioi.com/submission/928510">Link</a></p><h1 id="problem-b-httpszhengruioicomproblem3282cid1952">Problem-B <a href="https://zhengruioi.com/problem/3282?cid=1952">探险</a></h1><h2 id="">题目描述</h2><p>给定一个数组 $a$，有两个人轮流操作，每次交换一对 $i,j$，谁进行操作后数组 $a$ 有序（从小到大），则这个人获胜。同时有 $q$ 个修改操作，分为两类：</p><ul><li>$1$ $i$ $j$：将 $a_i$ 与 $a_j$ 交换。</li><li>$2$ $k$：将 $a$ 循环位移 $k$ 次。
你需要对于初始状态和每一个修改后的状态，判断是先手获胜还是后手获胜。</li></ul><h2 id="">思路</h2><p>赛场上想到了逆序对，但是时间比较少，只剩 $15$ 分钟了，而且最后开的这道题，所以脑子不太好使，就没有想出来。</p><p>赛后发现赛时的思路大致是对的，但是没有继续思考下去。容易发现答案和逆序对个数的奇偶性有关，具体来说：</p><ul><li>当逆序对个数为 <strong>奇数</strong> 时，先手获胜；</li><li>当逆序对个数为 <strong>偶数</strong> 时，后手获胜
所以问题落在了如何处理修改后的逆序对变化。</li></ul><p>对于第一个操作是好求的，因为交换两个数，逆序对个数的奇偶性就会变化。而对于第二个操作，可以发现每一个跨过 $k$ 的两个数的逆序对都会反过来，所以对答案的影响就是 $k(n-k) \bmod 2$（赛时就是这里没有想出来）。</p><p>提交记录：<a href="https://zhengruioi.com/submission/929704">Link</a></p><h1 id="problem-c-httpszhengruioicomproblem3283cid1952">Problem-C <a href="https://zhengruioi.com/problem/3283?cid=1952">帕奇欧</a></h1><h2 id="">题目描述</h2><p>初始时，你手上有 1 张 A 类牌和 1 张 B 类牌。每一回合你都可以从拥有的牌中等概率选取一张，然后获得一张与抽取出来的牌同类型的牌，问你经过无限轮操作后，拥有 $x$ 张 A 类牌和 $y$ 张 B 类牌的概率，对 $10^9 + 7$ 取模。</p><h2 id="">思路</h2><p>手玩一下样例可以发现，答案为 $\frac{(x + y - 3)! * (x + y - 2)}{(x + y - 1)}$ (这是赛时没化简得结果)。</p><p>化简后是 $\frac{1}{x + y - 1}$。</p><p>提交记录：<a href="https://zhengruioi.com/submission/927967">Link</a></p><h1 id="problem-d-httpszhengruioicomproblem3284cid1952">Problem-D <a href="https://zhengruioi.com/problem/3284?cid=1952">比赛</a></h1><h2 id="">题目描述</h2><p>有一种比赛，每一次比赛有若干个小节，率先得到 $M$ 分得人赢下这个小节。给定 $x,y,M$，求最后总分为 $x,y$ 时不同得小节数。</p><h2 id="">思路</h2><p>赛时推了下式子，没推出来 TAT。</p><p>首先可以发现的是，满足题目要求得小节数是一段连续的区间。</p><p>所以我们可以分别找到这个区间的上界和下界。容易发现上界为 $\lfloor \frac{x}{M} \rfloor + \lfloor \frac{y}{M} \rfloor$。而下界通过分析可以发现是 $\max(\lceil \frac{x - tx}{M} \rceil, \lceil \frac{y - ty}{M} \rceil, \lceil \frac{x + y}{M - 1} \rceil)$，其中 $tx = \lfloor \frac{x}{M} \rfloor, ty = \lfloor \frac{y}{M} \rfloor$。</p><p>提交记录：<a href="https://zhengruioi.com/submission/929782">Link</a></p><h1 id="problem-e-httpszhengruioicomproblem3285cid1952">Problem-E <a href="https://zhengruioi.com/problem/3285?cid=1952">三目运算符</a></h1><h2 id="">题目描述</h2><p>给定一个只包含 $0,1,x,?,:$ 的合法三目运算符表达式，你可以将其中的 $x$ 替换为 0/1，求所有表达式的值的和对 $10^9 + 7$ 取模的结果。</p><h2 id="">思路</h2><p>赛时直接枚举 $x$ 的取值，竟然有 <em>80pts</em> 的暴力分，出题人还是太良心了。</p><p>正解是考虑和表达式树一样的结果，即对于每个节点，都有走向 0/1 的两条路，问题转化为了走向值为 1 的叶子节点的概率。</p><p>提交记录：<a href="https://zhengruioi.com/submission/929949">Link</a></p><h1 id="problem-f-httpszhengruioicomproblem3286cid1952">Problem-F <a href="https://zhengruioi.com/problem/3286?cid=1952">卡牌游戏</a></h1><h2 id="">题目描述</h2><p>你有三种不同类型的卡牌，其中每种卡牌的数量由给定的字符串决定。同时你拥有两个数组 $A,B$，你需要从卡牌中选取恰好 $k$ 张，设你选择的三种卡牌的数量分别为 $x,y,z$，则你的得分为 $A_x \times B_y \times 2^z$。求最大得分。</p><h2 id="">思路</h2><p>赛时直接枚举前两种卡牌的个数，得了 20 分。</p><p>可以发现第三种卡牌的贡献最大。设最多能选 $t$ 张第三种卡牌，则所有 $z &lt; t-60$ 的方案均更差。即使 $A_x,B_y$ 的值从 $10^9 \times 10^9$ 变为了 $1 \times 1$，即变为原来的 $\frac{1}{10^{18}}$，而 $2^60 &gt; 10^{18}$，所以变化后的结果依然更优，所以只要考虑 $t - 60 \le z \le t$ 的方案即可。</p><p>提交记录：先欠着吧。</p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day24#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day24</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day24</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:52:17 GMT</pubDate></item><item><title><![CDATA[2025 Summer Day23]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://unleafy.cn/posts/record/2025-summer-Day23">https://unleafy.cn/posts/record/2025-summer-Day23</a></blockquote><div><h1 id="">题目</h1><h2 id="codeforces-p1672ehttpscodeforcescomproblemsetproblem1672e-notepadexe"><a href="https://codeforces.com/problemset/problem/1672/E">CodeForces-P1672E</a> notepad.exe</h2><h3 id="">题目大意</h3><p>交互题。
有 $n$ 个长度分别为 $l_i$ 的单词，要求将这些单词排放在一个记事本当中，每两个单词之间需要空格（换行不需要）。你最多可以询问 $n+30$ 次，每次可以询问一个宽度 $w$，裁判会告诉你至少需要高度为 $h$ 的记事本才可以放下所有的单词，求 $\min{ hw }$。</p><h3 id="">思路</h3><p>考虑先用 30 次找出将所有单词放在一行所需的最少宽度 $w_0$，然后对于每一个 $1 \le i \le n$，询问排列成 $i$ 行需要的最少行数，询问次数正好 $n + 30$ 次。</p><p>提交记录：<a href="https://vjudge.net/solution/62683276">Link</a></p><h2 id="codeforces-1010bhttpswwwcodeforcescomproblemsetproblem1010b-rocket"><a href="https://www.codeforces.com/problemset/problem/1010/B">CodeForces-1010B</a> Rocket</h2><h3 id="">题目大意</h3><p>交互题。
你可以问裁判 60 次问题，裁判会根据一个循环节长度为 $n$ 的数组回答你的问题：如果 $a_i = 0$，则第 $i$ 次回答的是假话；如果 $a_i = 1$，则第 $i$ 次回答的是真话。你并不知道这个数组 $a$。你需要猜一个数 $x (1 \le x \le m)$。每次询问你可以问裁判一个数，他会回答你这个数是 <strong>大于(1)</strong>、<strong>等于(0)</strong>、<strong>小于(-1)</strong>（遵守之前的规则）。</p><h3 id="">思路</h3><p>首先范围 $m \le 10^9 &lt; 2^{30}$，所以我们可以先用 30 次机会，每次询问 1，就可以得到数组 $a$。然后二分答案 $x$ 就可以了，总询问次数为 60 次。</p><p>提交记录：<a href="https://vjudge.net/solution/62685613">Link</a></p><h2 id="luogu-p1337httpswwwluogucomcnproblemp1337----xxx"><a href="https://www.luogu.com.cn/problem/P1337">Luogu-P1337</a> 平衡点 / 吊打 XXX</h2><h3 id="">题目大意</h3><p>求 $n$ 个点的带权费马点。</p><h3 id="">思路</h3><p>考虑使用模拟退火，随机检查，剩下的就交给阳寿吧（WA $\times$ 6）。</p><p>提交记录：<a href="https://www.luogu.com.cn/record/list?pid=P1337&amp;user=1023191">Link</a></p><h2 id="luogu-p5285httpswwwluogucomcnproblemp5285--"><a href="https://www.luogu.com.cn/problem/P5285">Luogu-P5285</a> [十二省联考] 骗分过样例</h2><p>写这道题上来只是小小的心灵受到了大大的震撼。怎么做到再 $20$ 组数据中找规律的啊！</p></div><p style="text-align:right"><a href="https://unleafy.cn/posts/record/2025-summer-Day23#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://unleafy.cn/posts/record/2025-summer-Day23</link><guid isPermaLink="true">https://unleafy.cn/posts/record/2025-summer-Day23</guid><dc:creator><![CDATA[Unleafy]]></dc:creator><pubDate>Thu, 05 Mar 2026 12:51:36 GMT</pubDate></item></channel></rss>