题意
给出 \(n\) 个区间 \([l_i, r_i)\) 和 \(m\) 个询问,每次询问给出一个询问区间 \([L_i, R_i]\),要求求出随机选取 \(ll,rr\) 使得 \(L_i \le ll \le rr \le R_i\),第 \(ll\) 到第 \(rr\) 个询问区间的区间并的大小的期望。
sol
注: 下文所述区间中,小写字母表示给定的值或区间(值域为 \([1,10^9]\)),大写字母为给出的询问点或询问区间(值域为 \([1,n]\));为简化问题,题中所述左闭右开区间均改为闭区间
容易发现给定 \(L_i, R_i\) 后,存在的询问区间个数为 \(\dfrac{(L_i - R_i + 1)(L_i - R_i + 2)}{2}\) 个,因此本题是假期望,求出 \([L_i,R_i]\) 中所有子询问区间的答案之和即可,这个问题有点困难,先来考虑简化版本:给出 \(n\) 个区间 \([l_i, r_i]\) 和 \(m\) 个询问,每次询问给出一个询问区间 \([L'_i, R'_i]\),要求求出第 \(L'_i\) 到第 \(R'_i\) 个区间的区间并的大小
可以记录值域中每个点会对哪段区间产生贡献,即对于每个点记录最后一次被第几个区间覆盖。当修改时,记原值为 \(T\),新值为 \(I\),那么对于所有右端点在 \(I\) 右侧(包括 \(I\))的询问区间,左端点为 \((T,I]\) 中的所有询问都可以增加 \(1\) 的贡献,使用线段树/树状数组维护即可。这个解法是正确的,但是它有一个问题:值域为 \(10^9\),暴力维护是不可接受的;仔细观察发现,\(I\) 和 \(T\) 相同的部分会形成连续段,并且可以一起维护,一起造成大小为连续段长度的贡献。因此,我们需要一个快速维护连续段的数据结构,即珂朵莉树。珂学家上线了(bushi 这东西嘎嘎简单,因此出门左转 OI-Wiki。
再来考虑子区间并的和:方法和刚才相同(使用珂朵莉树暴力维护连续段被第几个区间覆盖,并且处理这些连续段),只需要考虑贡献即可:设询问区间为 \([L,R]\),如果 \(L \in (T,I]\),那么获得贡献的区间中,左端点有 \(I-L+1\) 种,如果 \(L \in [1, T]\),那么获得贡献的区间中,左端点有 \(I-T\) 种。但无论怎样,只要 \(R \ge I\),那么右端点都有 \(R-I+1\) 种,因此产生的贡献为(记连续段长度为 \(len\))
由于我们在合并贡献时并不知道 \(L,R\) 的值,但是容易发现,这两个多项式都满足 \(ALR + BL + CR + D\) 的形式,并且这样的多项式的系数满足可加性,因此可以类似于 [lnsyoj2286/luoguP4458/BJOI2018]链上二次求和 维护这四项的系数,在计算的时候直接代入即可
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#include <vector>
#define x first
#define y secondusing namespace std;
typedef long long LL;
typedef pair<int, int> PII;const int N = 200005, mod = 1e9 + 7;int n, m;
int ql[N], qr[N];
int ans[N];struct elem {int l, r, v;elem(int _l, int _r, int _v): l(_l), r(_r), v(_v){}bool operator< (const elem &W) const {return l < W.l;}
};
set<elem> odt;struct Node {int l, r, lr, k;Node(){l = 0, r = 0, lr = 0, k = 0;}Node(int _l, int _r, int _lr, int _k): l(_l), r(_r), lr(_lr), k(_k){}Node operator+ (const Node &W) const {return {(l + W.l) % mod, (r + W.r) % mod , (lr + W.lr) % mod, (k + W.k) % mod};}Node operator- () const {return {-l, -r, -lr, -k};}int calc(int ll, int rr){return (((LL) ll * l % mod + (LL) rr * r % mod + (LL) ll * rr % mod * lr % mod + k) % mod + mod) % mod;}
}tr[N];PII ranges[N];
vector<PII> queries[N];int lowbit(int x){return x & -x;
}void add(int x, Node val) {x = n - x + 1;for (; x <= n; x += lowbit(x)) tr[x] = tr[x] + val;
}Node query(int x) {x = n - x + 1;Node ans(0, 0, 0, 0);for (; x; x -= lowbit(x)) ans = ans + tr[x];return ans;
}auto split(int x){auto it = odt.lower_bound({x, 0, 0});if (x == it->l) return it;it -- ;int l = it->l, r = it->r, v = it->v;odt.erase(it);odt.insert({l, x - 1, v});return odt.insert({x, r, v}).x;
}void assign(int l, int r, int v) {auto itr = split(r + 1), itl = split(l);odt.erase(itl, itr);odt.insert({l, r, v});
}void perform(int l, int r, int v){auto itr = split(r + 1), itl = split(l);for (auto it = itl; it != itr; it ++ ) {int ll = it->l, rr = it->r, vv = it->v;int len = rr - ll + 1;add(v, {(LL) (v - 1) * len % mod, (LL) (v + 1) * len % mod, -len, (LL) (v + 1) * (-v + 1) % mod * len % mod});add(vv, -(Node) {(LL) (v - 1) * len % mod, (LL) (v + 1) * len % mod, -len, (LL) (v + 1) * (-v + 1) % mod * len % mod});add(vv, {0, (LL) (v - vv) * len % mod, 0, (LL) (v - vv) * (-v + 1) % mod * len % mod});}
}int qpow(int a, int k, int p){int ans = 1;while (k){if (k & 1) ans = (LL) ans * a % p;a = (LL) a * a % p;k >>= 1;}return ans;
}int main(){scanf("%d%d", &n, &m);for (int i = 1; i <= n; i ++ ) scanf("%d%d", &ranges[i].x, &ranges[i].y), ranges[i].y -- ;for (int i = 1; i <= m; i ++ ){scanf("%d%d", &ql[i], &qr[i]);queries[qr[i]].push_back({ql[i], i});}odt.insert({1, 1000000001, 0});for (int i = 1; i <= n; i ++ ){int l = ranges[i].x, r = ranges[i].y;perform(l, r, i);assign(l, r, i);for (auto q : queries[i]) ans[q.y] = query(q.x).calc(q.x, i);}for (int i = 1; i <= m; i ++ ) {int len = qr[i] - ql[i] + 1;printf("%lld\n", (LL) ans[i] * qpow((LL) len * (len + 1) / 2 % mod, mod - 2, mod) % mod);}
}