1 条题解

  • 0
    @ 2026-5-5 1:49:46

    P8394 「BalticOI 2022 Day2」Boarding Passes

    一道不算难的状压。

    根据期望的线性性,将组 AA 上船的代价拆成 AA 内部所有人之间的代价以及其它所有已经上船的组对 AA 产生的代价。

    对于组 AA 而言,必然存在一个分界点 pp 满足位置 p\leq p 的人从左边上船,>p> p 的人从右边上船。考虑如果 i<ji < jii 从右边上船,jj 从左边上船,容易证明将它们调整为 ii 从左边上船,jj 从右边上船代价必然减小。

    假设在 pp 处分割时从左边上船的人数为 xx,则从右边上船的人数为 Ax|A| - x。考虑任意两对从左边上船的人,当位置在左边的人先于位置在右边的人上船时,会产生 11 的代价。因为随机排列中某两个数具有特定的前后顺序的概率相等,均为 12\dfrac 1 2,所以分割方案 pp 的组内贡献为 cA=x(x1)+(Ax)(Ax1)4c_A = \dfrac {x(x - 1) + (|A| - x)(|A| - x - 1)} 4。不妨改变 pp 的定义为在 ApA_p 处分割,也就是前 pp 个人从左边上船,后 Ap|A| - p 个人从右边上船,则 cAc_A 是关于 pp 的二次函数,且具有下凸性。

    考虑某一组 BBAA 的贡献。如果 AiA_i 从左边上船,那么它的贡献是 <Ai< A_iBB 的数量,从右边同理。因此,预处理出 fB,A,pf_{B, A, p} 表示如果在 ApA_p 处分割,那么 BB 对在左边上船的 AA 的总贡献,gB,A,pg_{B, A, p} 表示 BB 对在右边上船的 AA 的总贡献。预处理枚举任意两组和分隔位置,时间复杂度 O(g2n)\mathcal{O}(g ^ 2n)O(gn)\mathcal{O}(gn),视实现细节程度而定。

    考虑 ApA_pAp+1A_{p + 1},因为 <Ap+1< A_{p + 1}BB 的数量显然不小于 <Ap< A_pBB 的数量,所以 fB,Af_{B, A} 具有下凸性。同理 gB,Ag_{B, A} 具有下凸性。

    因为我们只关心有哪些组此时已经上船,而非它们具体的上船顺序,自然考虑状压 DP ansSans_S 表示上船的组的集合为 SS 时的最小代价。转移枚举不属于 SS 的组 AA,然后每个可能的分割点以及每个 BSB\in S,容易计算贡献。这样暴力的时间复杂度为 O(2ggn)\mathcal{O}(2 ^ g gn)(有个 gg 可以被均摊分析掉,但是没啥影响),无法接受。

    但注意到 cAc_AfB,Af_{B, A}gB,Ag_{B, A} 均下凸,所以贡献单峰(显然,下凸是比单峰更强的条件),三分最优分割点位置即可。时间复杂度 O(2gg2logn)\mathcal{O}(2 ^ g g ^ 2\log n)

    #include <bits/stdc++.h>
    using namespace std;
    bool Mbe;
    constexpr int N = 1e5 + 5;
    template <class T> inline void cmin(T &x, T y) {x = x < y ? x : y;}
    int n, mx;
    char s[N];
    vector<int> buc[N];
    long long f[15][15][N], g[15][15][N];
    long long ans[1 << 15];
    bool Med;
    int main() {
      fprintf(stderr, "%.3lf\n", (&Mbe - &Med) / 1048576.0);
      // freopen("passes.in", "r", stdin);
      // freopen("passes.out", "w", stdout);
      scanf("%s", s + 1);
      n = strlen(s + 1);
      for(int i = 1; i <= n; i++) {
        mx = max(mx, (int) (s[i] - 'A') + 1);
        buc[s[i] - 'A'].push_back(i);
      }
      for(int i = 0; i < mx; i++)
        for(int j = 0; j < mx; j++)
          if(i != j) {
            for(int k = 1, cur = 0; k <= n; k++) {
              f[i][j][k] = f[i][j][k - 1];
              if(s[k] == i + 'A') cur++;
              if(s[k] == j + 'A') f[i][j][k] += cur;
            }
            for(int k = n, cur = 0; k; k--) {
              g[i][j][k] = g[i][j][k + 1];
              if(s[k] == i + 'A') cur++;
              if(s[k] == j + 'A') g[i][j][k] += cur;
            }
          }
      memset(ans, 0x3f, sizeof(ans));
      ans[0] = 0;
      for(int i = 0; i < 1 << mx; i++) {
        for(int j = 0; j < mx; j++)
          if(i >> j & 1 ^ 1) {
            int l = 0, r = buc[j].size();
            auto calc = [&](int p) {
              int pos = p ? buc[j][p - 1] : 0;
              int back = buc[j].size() - p;
              long long tot = 1ll * p * (p - 1) + 1ll * back * (back - 1);
              tot >>= 1;
              for(int k = 0; k < mx; k++)
                if(i >> k & 1)
                  tot += f[k][j][pos] + g[k][j][pos + 1] << 1;
              return tot;
            };
            while(l + 2 < r) {
              int m1 = l + r >> 1, m2 = m1 + 1;
              if(calc(m1) <= calc(m2)) r = m2;
              else l = m1;
            }
            long long coef = 1e18;
            for(int p = l; p <= r; p++) cmin(coef, calc(p));
            cmin(ans[i | (1 << j)], ans[i] + coef);
          }
      }
      printf("%.3lf\n", ans[(1 << mx) - 1] * .5);
      return 0;
    }
    
    • 1

    信息

    ID
    7272
    时间
    2000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者