等价命题:
给定一些整数区间。现在要把这些区间分割成两个分割集合A和B,然后求
max
A
,
B
∣
(
⋂
r
∈
A
r
)
∪
(
⋂
r
∈
B
r
)
∣
\max_{A,B}{\left |(\bigcap_{r\in A}r)\cup(\bigcap_{r\in B}r)\right |}
A,Bmax∣∣∣∣∣(r∈A⋂r)∪(r∈B⋂r)∣∣∣∣∣
先找出区间左端点最大的区间s和右端点最小的区间t
(1)若s,t同属于一个分割集合,那么该分割集合不管再加什么,其交集出来的结果都跟仅含有s,t的区间长度相同。所以这时这个集合除了放s,t外,可以任意放其他区间都不会改变这个分割集合的最终区间长度。那么另一个分割集合显然取所有区间最大的放入,即可算出当前情况下的最大值。
O
(
n
)
O(n)
O(n)
(2)若s,t分属两个分割集合,设s所在分割集合为A,t所在分割集合为B,那么A中的任意区间的右端点到s的左端点为有效区间,设
a
i
=
max
(
r
i
−
l
(
s
)
+
1
,
0
)
a_i=\max (r_i-l(s)+1,0)
ai=max(ri−l(s)+1,0)那么A中所有区间的交集的区间大小就是A中所有区间所对应
a
i
a_i
ai的最小值。
同理可设
b
i
=
max
(
r
(
t
)
−
l
i
+
1
,
0
)
b_i=\max (r(t)-l_i+1,0)
bi=max(r(t)−li+1,0)
表示B中各个区间的有效个数。B中所有区间的交集的区间大小就是B中所有区间所对应
b
i
b_i
bi的最小值。
总结来看就是求哪个分割下,两个集合的
a
i
a_i
ai的最小值和
b
i
b_i
bi的最小值的和最大。
如果一个一个枚举就变成了
O
(
n
2
)
O(n^2)
O(n2)来不及,这里我的做法是把
a
i
a_i
ai和
b
i
b_i
bi做成pair,然后全部塞进vector里面,对vector从小到大排序后,开始依次遍历vector:记录
b
i
b_i
bi的最小值,同时
a
i
a_i
ai是排好序的,所以能确定当前
a
i
a_i
ai是
i
i
i之后所有
a
i
a_i
ai的最小值,很自然的,我们可以把
i
i
i之后的作为分割集合A,因为最小值就是
a
i
a_i
ai那么
i
−
1
i-1
i−1及这之前的看做分割集合B,又因为
b
i
b_i
bi的最小值我们一直在记录,所以每次循环能立刻取到。排序复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn),最后的枚举为
O
(
n
)
O(n)
O(n),整体压缩在
O
(
n
log
n
)
O(n\log n)
O(nlogn)
下面是cpp风格的伪代码:
#include<bits/stdc++.h>
using namespace std;
//省略一些宏
//---------------------
#define MAXN 100005
#define INF 1000000007
//---------------------
ll n;
ll l[MAXN],r[MAXN];
vector<pll> arr;
int main(){
cin >> n;
REP(i,n) cin >> l[i] >> r[i];
ll res = 0;
ll lmax = 0;
ll rmin = INF;
ll nmax = 0;
REP(i,n) {
lmax = max(lmax,l[i]);
rmin = min(rmin,r[i]);
nmax = max(nmax,r[i]-l[i]+1);
}
res = max(rmin-lmax+1,0ll) + nmax;
REP(i,n){
arr.PB(pll(max(r[i]-lmax+1,0ll),max(rmin-l[i]+1,0ll)));
}
sort(ALL(arr));
ll minb = INF;
REP(i,n-1){
ll a = arr[i+1].P1, b = arr[i].P2;
minb = min(minb,b);
res = max(res,a+minb);
}
PRT(res);
return 0;
}
反思
上述思路由题解提供。一开始一直想用dp,滑动窗口,二分查找之类的骚操作,结果是带有分类讨论的贪心算法和前缀和,深刻感觉自己能力不足。之后遇到这样类似的思维题要善于简化题目和思维回路,多动笔找一找等价的题目表达为上策。