CF1034D Intervals of Intervals
分析:
题目要求计算前 \(k\) 大区间价值和,不妨先计算第 \(k\) 大区间价值。
使用二分,每次计算价值大于等于 \(mid\) 的区间个数。区间的数量级是 \(O(n^2)\) 的。但可以发现对于一个固定的右端点 \(r\),\(f_{l}\)(表示区间 \([l,r]\) 的价值) 单调不升。二分出最后一个 \(f_{l} \ge mid\) 的位置 \(h\),将满足条件的区间个数加上 \(h\)。
考虑使用扫描线维护 \(f_{l}\)。记 \(lst_{i}\) 表示到枚举到右端点 \(P\) 为止,位置 \(i\) 最后一次被哪个区间覆盖(不包括 \(P\))。对于一次更新 \(lst_{i} \leftarrow P \ (i \in [l_{P},r_{P}])\),在 \(f\) 上对应的修改为 \(f_{x} \leftarrow f_{x}+1 \ (x \in [lst_{i}+1,P])\)(区间修改用线段树维护)。利用 set 维护 \(lst_{i}\) 相同的区间。由于每次更新最多只会生成两个区间,每个区间只会被删除一次。因此可以做到 \(O(n \log n)\) 维护 \(f\)。
得到第 \(k\) 大区间价值后,再使用一个线段树即可迅速求出前 \(k\) 大区间价值和(这部分时间复杂度为 \(O(n \log n)\))。至此,我们花费 \(O(n \log n \log V)\) 解决了这个问题。
上述算法无法通过,二分 \(mid\) 是无法避免的,复杂度瓶颈在于:
- 每次二分 \(mid\) 后,使用了一遍 \(set\):预处理出 \(set\) 带来的区间修改即可。
- 二分 \(h\):随着 \(P\) 的增大,整个 \(f\) 单调不降,那么 \(h\) 也就单调不减,直接双指针即可避免二分。
- 区间加单点查询使用了线段树:由于单点查询的位置 \(h\) 逐渐变大,利用差分可以 \(O(1)\) 修改。具体地,记当前 \(f_{h}=z\),对于一次修改 \(upd(l,r,x)\),将 \(G_{l} \leftarrow G_{l}+x, \ G_{r+1} \leftarrow G_{r+1}-x\),如果 \(l\) 小于等于 \(h\),直接将 \(z\) 加上 \(x\)。\(h\) 变大时 \(z\) 加上 \(G_{h}\) 即可。
计算价值和可以使用线段树,也可以 \(O(n)\) 计算。总时间复杂度都是 \(O(n (\log n + \log V))\),只不过前者需卡常。
代码:
#include<bits/stdc++.h>
#define N 600005
#define int long long
using namespace std;
struct node {
int l, r, val;
friend bool operator < (node x, node y) {
return x.l < y.l;
}
};
set<node>s;
int n, ans, k;
int l[N], r[N];
void split(int x) { //使得存在有一个区间的左端点为 x
auto Get = s.upper_bound((node){x, 0, 0});
Get--;
node now = (*Get);
if(now.l == x) return;
s.erase(now);
s.insert((node){now.l, x - 1, now.val});
s.insert((node){x, now.r, now.val});
}
node Del[N];
int tot;
vector<node>p[N];
void upd(int l, int r, int Num) { //把 val全改成 Num
auto Get = s.lower_bound((node){l, 0, 0});
tot = 0;
while(1) {
node now = *Get;
if(now.l > r) break;
Del[++tot] = now;
p[Num].push_back((node){now.val + 1, Num, now.r - now.l + 1});
Get++;
}
for(int i = 1; i <= tot; i++) s.erase(Del[i]);
s.insert((node){l, r, Num});
}
int G[N];
int Sol1(int mid) { //计算权值大于等于mid的区间个数
int tot = 0;
int h = 0, z = 0;
for(int i = 1; i <= n; i++) G[i] = 0;
for(int i = 1; i <= n; i++) {
for(auto now : p[i]) {
if(now.l <= h) z += now.val;
G[now.l] += now.val, G[now.r + 1] -= now.val;
}
while(h < i && z + G[h + 1] >= mid) {
h++;
z += G[h];
}
tot += h;
}
return tot;
}
int Sol2(int mid) { //计算权值大于等于mid的区间权值和
int sum = 0, S = 0;
int h = 0, z = 0;
for(int i = 1; i <= n; i++) G[i] = 0;
for(int i = 1; i <= n; i++) {
for(auto now : p[i]) {
if(now.l <= h) sum += (h - now.l + 1) * now.val, z += now.val;
G[now.l] += now.val, G[now.r + 1] -= now.val;
}
while(h < i && z + G[h + 1] >= mid) {
h++;
z += G[h];
sum += z;
}
S += sum;
}
return S;
}
signed main() {
// freopen("x.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i++) {
cin >> l[i] >> r[i];
r[i]--;
}
s.insert((node){1, (int)1e9, 0});
for(int i = 1; i <= n; i++) {
split(l[i]); split(r[i] + 1);
upd(l[i], r[i], i);
}
int L = 1, R = 1e9, mid;
while(L <= R) {
mid = (L + R) / 2;
if(Sol1(mid) >= k) ans = mid, L = mid + 1;
else R = mid - 1;
}
//cout << ans << endl;
cout << Sol2(ans) - (Sol1(ans) - k) * ans;
return 0;
}