题解:CF1889C1-Doremy’s Drying Plan (Easy Version)
一、 题意描述
1. 题目链接
(1) CF链接
(2) 洛谷链接
2. 题目翻译
有一个长度为
n
n
n 的序列,上面有
n
n
n 个点,编号为
1
1
1 到
n
n
n。现在给定
m
m
m 个区间,第
i
i
i 个区间覆盖了序列中的
l
i
l_i
li 到
r
i
r_i
ri 之间(包含两端)的所有点。你可以删除其中的
2
2
2 个线段,使得序列中未被任何线段覆盖的点数最大。
1
≤
n
≤
2
⋅
1
0
5
1\le n\le 2\cdot 10^5
1≤n≤2⋅105,
2
≤
m
≤
2
⋅
1
0
5
2 \le m \le 2\cdot 10^5
2≤m≤2⋅105,
k
=
2
k = 2
k=2。
∑
n
+
m
≤
2
⋅
1
0
5
\sum n+m\le2\cdot 10^5
∑n+m≤2⋅105
二、 思路分析
观察数据范围,
n
n
n 和
m
m
m 都是
1
0
5
10^5
105 级别,所以考虑
O
(
n
+
m
)
O(n+m)
O(n+m) 左右的算法,当然带上几个小
l
o
g
log
log 也没问题。
对于序列中所有的点,我们可以对它们进行分类:
①被
0
0
0 个区间覆盖;
②被
1
1
1 个区间覆盖;
③被
2
2
2 个区间覆盖;
④被
3
3
3 个或者更多个区间覆盖。
想要求出某个点是哪一类,我们可以在读入线段时记录差分。
显然,对于①类点,无论选择哪两条,它们都是“未被任何线段覆盖的点”,所以直接计入总数。
对于④类点,无论怎么选都无法让它们变成“未被任何线段覆盖的点”,所以它们被我们抛弃了。
如果只能删除一个线段,那么对最终答案所能产生的贡献自然是这个线段内②类点的数量,为了迅速求出它,我们需要维护一个前缀和,存储在某个点之前有多少个②类点。
显然,我们可以把每个线段按照这种贡献从大到小排序,选取最大的两个,把它们的贡献相加计入总数。
问题:这样求出的贡献一定是最大的吗?
显然不是!我们只能把贡献计入一个存储最终贡献的最大值的变量。如果两个线段有重叠,那么最终的贡献还要加上中间重叠部分的③类点总数(这个我们也用前缀和维护),但是如果继续用刚才的方法进行排序取出的最大两个有可能不是最优的。
于是我们考虑,被两个线段覆盖的点有哪些?我们可以遍历一遍每个节点,通过前缀和求出它被多少区间覆盖。但问题又来了,到底是哪两个区间呢?我们可以把线段的左端点和右端点加一分别扔进两个序列(记录一下差分维护的过程),按照端点下标排序,并记录到底是哪个线段,在遍历节点之前,用一个集合来维护那些线段覆盖到遍历到的点,最后遍历到每个点
i
i
i,左端点的序列里端点下标为
i
i
i 的元素先把对应的线段编号扔进这个集合,再把它从序列中删除,右端点加一的序列同理,只不过是把里面的线段编号从集合里删除。最后如果集合里是两条线段,就计算这两条线段的贡献,和之前的贡献取最大值。最后把贡献最大值累加到①类点数量才是答案。
具体实现还要看代码。
三、 时间代价
算法主体遍历线段、节点是 O ( n ⋅ m ) O(n\cdot m) O(n⋅m) 的,但是维护一大堆集合和序列用到 s e t set set,需要乘上一个 O ( l o g ( m ) ) O(log(m)) O(log(m)),所以总共时间复杂度可以估计成 O ( ( n + m ) ⋅ l o g ( m ) ) O((n+m)\cdot log(m)) O((n+m)⋅log(m))
四、代码实现
#include<bits/stdc++.h>
#define N 220000
using namespace std;
multiset<pair<int,int>>in={},out={};
multiset<int>inc1={};
set<int>xd={};
int c[N]={},c1[N]={},c2[N]={},l[N]={},r[N]={},s[N]={},s1[N]={},s2[N]={},ans=0,inc=0,k=0,m=0,n=0,t=0;
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n+1;i++){
c[i]=0;
c1[i]=0;
c2[i]=0;
}
in.clear();
out.clear();
for(int i=1;i<=m;i++){
scanf("%d%d",&l[i],&r[i]);
c[l[i]]++;
c[r[i]+1]--;
in.insert({l[i],i});
out.insert({r[i]+1,i});
}
ans=0;
for(int i=1;i<=n;i++){
s[i]=s[i-1]+c[i];
if(s[i]==0){
ans++;
}else{
if(s[i]==1){
c1[i]++;
}else{
if(s[i]==2){
c2[i]++;
}
}
}
s1[i]=s1[i-1]+c1[i];
s2[i]=s2[i-1]+c2[i];
}
inc1.clear();
for(int i=1;i<=m;i++){
inc1.insert(-(s1[r[i]]-s1[l[i]-1]));
}
inc=0;
int x=*inc1.begin();
inc1.erase(inc1.begin());
int y=*inc1.begin();
inc=max(inc,-(x+y));
xd.clear();
for(int i=1;i<=n;i++){
while(out.empty()==false&&out.begin()->first==i){
xd.erase(out.begin()->second);
out.erase(out.begin());
}
while(in.empty()==false&&in.begin()->first==i){
xd.insert(in.begin()->second);
in.erase(in.begin());
}
if(xd.size()==2){
auto now=xd.begin();
int a=*now;
now++;
int b=*now;
int ret=(s1[r[a]]-s1[l[a]-1])+(s1[r[b]]-s1[l[b]-1])+(s2[min(r[a],r[b])]-s2[max(l[a],l[b])-1]);
inc=max(inc,ret);
}
}
printf("%d\n",ans+inc);
}
return 0;
}