LOJ 4197 「BalticOI 2024」Flooding Wall

如果直接考虑每个 \(i\) 的贡献,那么同时还要知道前缀后缀的最大值中的最小值,这个枚举的数量级已经到了 \(\mathcal{O}\left(n^2\right)\),并不能接受。

于是不考虑从序列的每个位置来思考,而是转换维度去思考。
因为对于 \(i\) 的贡献是形如 \(\min(\operatorname{pre}, \operatorname{suf}) - h_i\) 的,可以来刻画这个差值的形式。
一个办法是考虑在数轴上点出 \(\min(\operatorname{pre}, \operatorname{suf}), h_i\) 这两个点,那么这两个点之间的距离就是 \(\min(\operatorname{pre}, \operatorname{suf}) - h_i\)
进一步的,考虑把数轴上两点的距离继续摊给比较朴素的信息,因为本身定义 \(i\)\(j\) 的距离其实也是通过“单位长度”来得到的,也就是 \(i\)\(j\) 的距离其实是 \(i, j\) 之间的“单位长度”的个数。
那么“单位长度”是什么?其实就是 \([w, w + 1]\) 这一段就是 \(1\) 的“单位长度”。
所以 \(\min(\operatorname{pre}, \operatorname{suf}) - h_i\) 可以摊到 \(h_i\le x < \min(\operatorname{pre}, \operatorname{suf})\) 的每一个“单位长度” \([x, x + 1]\) 上,让每一“单位长度”都被算到一次,就刻画出了这个差值。

也就是说,可以把问题转化为对于任意一个 \(x\)\([x, x + 1]\) 被“跨过”的次数。
这里指的“跨过”就是 \(h_i\le x < \min(\operatorname{pre}, \operatorname{suf})\)
又因为“跨过”的定义就只关心两个值与 \(x\) 的大小关系,所以只需要把值分为 \(0, 1\) 两类来考虑:\(\le x\)\(> x\)

接下来考虑,如果知道了 \(h_i\) 这个 \(01\) 序列的样子,如何算出答案。
那么因为只有 \(01\),所以抬升的过程只会涉及 \(0\)\(1\),所以考虑什么样的 \(0\) 才能到 \(1\)
手玩一下可以知道的是,夹在两个 \(1\) 之间的 \(0\) 就会抬升至 \(1\)
但是对于夹在 \(0\) 中的 \(1\) 并不好刻画,因为这涉及两个偏序关系,于是考虑刻画反面:不会被抬升的位置。
那么第一种是不被夹在 \(0\) 中的 \(1\),那么这只能是前缀或者后缀一段连续的 \(0\);第二种就是本身为 \(1\),那么就不会动。

于是考虑对总数及这两种情况分别计数,下记 \(c_i = [a_i > x] + [b_i > x]\),即 \(i\) 这个位置能填的 \(1\) 的数量。

  • 对于总数,就要求肯定要存在一个 \(1\),不然如果没有 \(1\) 前面的反面刻画就是错误的,考虑容斥,减掉没有 \(1\) 的方案数,并且每个位置都会先算到 \(1\) 次,所以是 \(2^n - \prod\limits_{i = 1}^n (2 - c_i)\)
  • 对于第二种情况,因为方案数 \(2^n\) 是确定的,所以可以考虑先求概率最后乘方案数得到。
    那么对于一个 \(i\),为 \(1\) 的概率就是 \(\frac{c_i}{2}\),所以其为 \(1\) 的数量就为 \(2^n \times \frac{c_i}{2} = 2^{n - 1}\times c_i\)
    对每个 \(i\) 求和得到 \(\sum\limits_{i = 1}^n 2^{n - 1}\times c_i = 2^{n - 1}\times \sum\limits_{i = 1}^n c_i\)
  • 对于第一种情况,首先只考虑前缀的 \(0\) 的情况,后缀是类似的。
    还是类似的,考虑计算概率,这是为了让最后的式子好看一些。
    接下来考虑刻画前缀的 \(0\),那么可以找一个代表元,就是前缀右边的那个 \(1\),也就是最左边的 \(1\)
    假设最左边的 \(1\) 在位置 \(i\),那么就有 \(i - 1\) 个前缀的 \(0\),且其概率分为两部分:前面 \(i - 1\) 个不为 \(1\)\(i\)\(1\),为 \(\frac{c_i}{2}\prod\limits_{j = 1}^{i - 1} (1 - \frac{c_j}{2})\)
    于是对 \(i\) 求和,要求的值就是 \(\sum\limits_{i = 1}^n (i - 1)\times \frac{c_i}{2}\times \prod\limits_{j = 1}^{i - 1} (1 - \frac{c_j}{2})\),这个值可以用线段树来维护,因为后面的乘法是一个前缀,可以通过再维护一个区间乘积来处理。

但是对于每个 \(x\) 重新处理肯定是不恰当的,于是可以考虑利用已有信息。
即按照 \(x\) 从小到大,那么每个 \(a_i, b_i\) 相对 \(x\) 大小关系只可能 \(1\to 0\) 并且只有 \(1\) 次,那么在更改大小关系时更新一下信息即可。

时间复杂度 \(\mathcal{O}(n\log n)\)

#include<bits/stdc++.h>
using ll = long long;
constexpr ll mod = 1e9 + 7, inv2 = mod + 1 >> 1;
constexpr int maxn = 5e5 + 10;
int n;
int a[maxn * 2], p[maxn * 2], c1[maxn];
ll mul[maxn * 4], suml[maxn * 4], sumr[maxn * 4];
inline void pushup(int k) {
   suml[k] = (suml[k << 1] + mul[k << 1] * suml[k << 1 | 1]) % mod;
   sumr[k] = (sumr[k << 1] * mul[k << 1 | 1] + sumr[k << 1 | 1]) % mod;
   mul[k] = mul[k << 1] * mul[k << 1 | 1] % mod;
}
inline void update(int x, int k = 1, int l = 1, int r = n) {
   if (l == r) {
      suml[k] = inv2 * c1[x] * (l - 1) % mod;
      sumr[k] = inv2 * c1[x] * (n - r) % mod;
      mul[k] = (1ll - inv2 * c1[x] % mod + mod) % mod;
      return ;
   }
   int mid = l + r >> 1;
   if (x <= mid) update(x, k << 1, l, mid);
   else update(x, k << 1 | 1, mid + 1, r);
   pushup(k);
}
ll f0; int sum1, c0;
inline void del(int id) {
   if (c1[id] == 2) c0--;
   else f0 = f0 * 2ll % mod;
   c1[id]--, sum1--;
   update(id);
}
int main() {
   scanf("%d", &n);
   for (int i = 1; i <= n * 2; i++) scanf("%d", &a[i]), p[i] = i;
   std::sort(p + 1, p + n * 2 + 1,
             [&](int x, int y) {
                return a[x] < a[y];
             });
   for (int i = 1; i <= n; i++) c1[i] = 2, update(i);
   ll pw = 1ll;
   for (int i = 1; i <= n; i++) pw = pw * 2ll % mod;
   f0 = 1ll, sum1 = n * 2, c0 = n;
   ll ans = 0ll;
   for (int i = 1, j = 1; ; i = j) {
      while (j <= n * 2 && a[p[i]] == a[p[j]]) {
         del((p[j] - 1) % n + 1), j++;
      }
      if (j > n * 2) break;
      ll tot = 0ll;
      tot = (tot + 1ll * n * (pw - (c0 ? 0ll : f0) + mod)) % mod;
      tot = (tot - 1ll * pw * inv2 % mod * sum1 % mod + mod) % mod;
      tot = (tot - pw * (suml[1] + sumr[1]) % mod + mod) % mod;
      ans = (ans + tot * (a[p[j]] - a[p[i]])) % mod;
   }
   printf("%lld\n", ans);
   return 0;
}
posted @ 2025-02-25 21:02  rizynvu  阅读(28)  评论(0)    收藏  举报