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;
} 
posted @ 2024-06-27 19:16  小超手123  阅读(4)  评论(0编辑  收藏  举报