2026 Autumn Training · 做题记录

PDF

CF1187F. Expected Square Beauty [done]

题意 独立均匀取整数 𝑋𝑖[𝑙𝑖,𝑟𝑖],求极大连续相等段数的平方期望。

数据范围 1𝑛2×1051𝑙𝑖𝑟𝑖109

Solution

考虑计算 𝐸[𝐵𝑘],其中 𝑘1,原题对应 𝑘=2。下标从 0 开始,取值区间转为 [𝐿𝑖,𝑅𝑖),末尾补充哨兵区间 [0,1)。记此时共有 𝑁 个变量、𝑚=𝑁1 条边,il𝑖=(𝑅𝑖𝐿𝑖)1。令 𝑌𝑖=[𝑋𝑖𝑋𝑖+1],则段数 𝐵=𝑖=0𝑚1𝑌𝑖

展开 𝐵𝑘 时,每项至多涉及 𝑘 条不同的边,而总边数为 𝑚,故只需统计 𝑗𝑞=min(𝑘,𝑚) 条边的贡献。每个选中连续段的长度不超过总选边数 𝑗,因此连续段长度也只需计算到 𝑞

先求 𝑤𝑙,𝑡,表示边段 [𝑙,𝑙+𝑡) 全部满足 𝑌𝑖=1 的概率。

连续段概率的容斥

变量区间 [𝑠,𝑟) 全部相等的概率为

𝑐(𝑠,𝑟)=max(0,min𝑠𝑖<𝑟𝑅𝑖max𝑠𝑖<𝑟𝐿𝑖)𝑖=𝑠𝑟1il𝑖.

𝑟=𝑙+𝑡+1。在相等事件的容斥展开中,按包含 𝑋𝑟1 的连通块 [𝑠,𝑟) 分类。块内 𝑟𝑠1 条边均选入容斥集合;若 𝑠>𝑙,分隔边 𝑠1 未选。因此

𝑤𝑙,𝑡=𝑠=𝑙𝑟1(1)𝑟𝑠1𝑐(𝑠,𝑟){1𝑠=𝑙𝑤𝑙,𝑠𝑙1𝑠>𝑙.

初值 𝑤𝑙,0=1。未选入容斥集合的边不施加相等约束。

𝑡 递增计算,固定 𝑟 后向左枚举 𝑠,维护区间交集与逆元乘积,即可常数时间更新 𝑐(𝑠,𝑟)

再求 dp𝑖,𝑗,表示边前缀 [0,𝑖) 中所有大小为 𝑗 的边集同时变化的概率之和。将选中的边分解为极大连续段,不同段之间至少空出一条边,依赖的原变量集合不交,因此联合概率为各段概率之积。

DP 转移

按末边是否被选分类。若被选,枚举最后一个极大连续段的长度 𝑡,其前一条边必须未选:

dp𝑖,𝑗=dp𝑖1,𝑗+𝑡=1min(𝑖,𝑗)𝑤𝑖𝑡,𝑡{[𝑗=𝑡]𝑖=𝑡dp𝑖𝑡1,𝑗𝑡𝑖>𝑡.

初值 dp0,0=1,其余状态为 0

𝑎𝑗=dp𝑚,𝑗=𝐸[(𝐵𝑗)]。指示变量的重复幂可以合并,按不同下标的数量分组,用第二类 Stirling 数还原:

𝐸[𝐵𝑘]=𝑗=0𝑞𝑆(𝑘,𝑗)𝑗!𝑎𝑗.

其中 𝑆(𝑘,𝑗)=𝑆(𝑘1,𝑗1)+𝑗𝑆(𝑘1,𝑗)𝑆(0,0)=1,其余边界状态为 0。代码用 𝑠𝑗 倒序滚动计算,最后得到 𝑠𝑗=𝑆(𝑘,𝑗)

总时间为 𝑂(𝑚𝑞2+𝑘𝑞),空间为 𝑂(𝑚𝑞)。原题 𝑘=2 时为线性规模。

C++
#include "noya/head.hpp"

#include "noya/modint.hpp"

using namespace noya;

using mi = mint107;

vc<vc<mi>> block_probabilities(const vl &L, const vl &R, int k) {
  int N = sz(L), m = N - 1, q = min(k, m);
  vc<mi> il(N);
  rep(i, N) il[i] = mi(R[i] - L[i]).inv();

  vc<vc<mi>> w(m, vc<mi>(q + 1));
  rep(l, m) {
    w[l][0] = 1;
    rep(t, 1, min(q, m - l) + 1) {
      int r = l + t + 1;
      ll ml = L[r - 1], mr = R[r - 1];
      mi prod = 1;
      for (int s = r - 1; s >= l; s--) {
        cmax(ml, L[s]);
        cmin(mr, R[s]);
        prod *= il[s];
        mi c = mi(max(mr - ml, ll(0))) * prod;
        mi pre = s == l ? mi(1) : w[l][s - l - 1];
        if ((r - s - 1) & 1)
          w[l][t] -= pre * c;
        else
          w[l][t] += pre * c;
      }
    }
  }
  return w;
}

vc<mi> factorial_moments(const vc<vc<mi>> &w, int k) {
  int m = sz(w), q = min(k, m);
  vc<vc<mi>> dp(m + 1, vc<mi>(q + 1));
  dp[0][0] = 1;
  rep(i, 1, m + 1) {
    dp[i] = dp[i - 1];
    rep(j, 1, min(i, q) + 1) {
      rep(t, 1, min(i, j) + 1) {
        int l = i - t;
        mi pre = l == 0 ? mi(j == t) : dp[l - 1][j - t];
        dp[i][j] += pre * w[l][t];
      }
    }
  }
  return dp[m];
}

mi expected_moment(const vl &L, const vl &R, int k) {
  assert(k >= 0 && !L.empty() && sz(L) == sz(R));
  auto w = block_probabilities(L, R, k);
  auto a = factorial_moments(w, k);
  int q = sz(a) - 1;
  vc<mi> s(q + 1);
  s[0] = 1;
  rep(n, k) {
    for (int j = min(n + 1, q); j > 0; j--) {
      s[j] = s[j - 1] + mi(j) * s[j];
    }
    s[0] = 0;
  }
  mi fac = 1, ans = s[0] * a[0];
  rep(j, 1, q + 1) {
    fac *= j;
    ans += s[j] * fac * a[j];
  }
  return ans;
}

void solve(int k = 2) {
  int n;
  cin >> n;
  vl L(n), R(n);
  rep(i, n) cin >> L[i];
  rep(i, n) {
    cin >> R[i];
    R[i]++;
  }
  L.push_back(0);
  R.push_back(1);

  cout << expected_moment(L, R, k).val() << "\n";
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);
  solve();
  return 0;
}

ABC476G. Increasing Popcount [done]

题意popcount(𝐿),,popcount(𝑅) 划分成尽量少的严格递增子序列,求最少个数。

数据范围 1𝑇1041𝐿𝑅1018

Solution

由 Dilworth 定理,答案等于 popcount 序列的最长不升子序列长度,相等值可以连续选取。

𝐵=60,将输入区间转为 [𝐿,𝑅)。按照线段树查询的方式,从左到右拆成 𝑂(𝐵) 个二进制对齐块。每次取不越过 𝑅、且 𝐿 为其长度倍数的最大块 [𝐿,𝐿+2𝑘)。令 𝑐=popcount(𝐿),块内值为 𝑐+popcount(𝑥),其中 0𝑥<2𝑘;值 𝑐+𝑡 出现 𝐶𝑘,𝑡=(𝑘𝑡) 次。

块内只需考虑选取某个固定的 popcount 值。对于任意非空不升子序列,都能在其最小值与最大值之间找到一层,改选该层的全部元素,长度不减,且仍能与两侧拼接。

为什么块内只需选择一个值

忽略共同的高位贡献 𝑐,对 𝑘 归纳。𝑘=0 时结论显然成立。对于 𝑘>0,将块均分为左右两半,右半的 popcount 比左半对应位置多 1。对非空的半块子序列应用归纳假设,可替换为该半块中某一值的全部元素,长度不减,且替换值仍在原子序列的值域内。

若只选了一半,直接在整块中选取同一个值,数量不会减少。否则,设左半选值 𝑎,右半选值 𝑏,必有 𝑎𝑏,两半的数量分别为 𝐶𝑘1,𝑎𝐶𝑘1,𝑏1

=𝑘12。由二项式系数的单峰性,当 𝑎>𝑏 时,可以进行以下调整:

  • 𝑎>,则 𝐶𝑘1,𝑎1𝐶𝑘1,𝑎,将左半所选值由 𝑎 改为 𝑎1
  • 𝑎,则 𝑏<𝑎,从而 𝐶𝑘1,𝑏𝐶𝑘1,𝑏1,将右半所选值由 𝑏 改为 𝑏+1

每次调整后仍选取相应值的全部元素,所选数量不减,且仍有 𝑎𝑏。同时,非负整数 𝑎𝑏 减少 1,因此有限次调整后必有 𝑎=𝑏

最终二者相等,记为 𝑣,此时选中的正是整个块中值为 𝑣 的一层,其大小为

𝐶𝑘1,𝑣+𝐶𝑘1,𝑣1=𝐶𝑘,𝑣.

所有调整后的值均落在初始的 [𝑏,𝑎] 内,因而仍在原子序列的值域内,不影响与块外两侧拼接,归纳完成。

dp𝑗 为已处理前缀中末项至少为 𝑗 的最长不升子序列长度,初值均为 0,表示空序列。追加一块时,选取值为 𝑗 的整层,贡献为 𝐶𝑘,𝑗𝑐;再对末值取后缀最大值。按 𝑗=𝑐+𝑘,,0 逆序更新:

dp𝑗max(dp𝑗+𝐶𝑘,𝑗𝑐,dp𝑗+1).

其中 𝑡<0𝑡>𝑘𝐶𝑘,𝑡=0。更新时 dp𝑗 仍是旧状态,dp𝑗+1 已是新状态,可直接原地转移。最终答案为 dp0

预处理二项式系数需 𝑂(𝐵2) 时间与空间;每组需 𝑂(𝐵2) 时间、𝑂(𝐵) 额外空间。

C++
#include "noya/head.hpp"

#include <bit>

using namespace noya;

constexpr int B = 60;
ll C[B + 1][B + 1];

void solve() {
  ll L, R;
  cin >> L >> R;
  R++;
  ll dp[B + 2]{};
  while (L < R) {
    int k = min(countr_zero(ull(L)), int(bit_width(ull(R - L))) - 1);
    int c = popcount(ull(L));
    for (int j = c + k; j >= 0; j--) {
      if (j >= c) dp[j] += C[k][j - c];
      cmax(dp[j], dp[j + 1]);
    }
    L += 1LL << k;
  }
  cout << dp[0] << "\n";
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);
  rep(k, B + 1) {
    C[k][0] = 1;
    rep(j, 1, k + 1) C[k][j] = C[k - 1][j - 1] + C[k - 1][j];
  }
  int t;
  cin >> t;
  while (t--) solve();
  return 0;
}

Comments

Sign in with GitHub to comment.