题解/算法 {D. Survey in Class}
LINK: https://codeforces.com/contest/1834/problem/D
;
假如最终的答案 最小值是a
区间 最大值是b
区间:
.
对于既不在a区间 又不在b区间的点, 如果选择该点 会导致他俩的值 都-=1
整体答案不变; (因此, 这类点 可以不选择);
因此, 我们只用关注 ab区间里的点; 令a区间长度为LA, b区间为LB, 他俩重叠区域长度为LL;
.
重叠的点 可以不选;
.
答案是: 要么选择LA - LL
这个区间 (答案是2 * (LA - LL)
), 要么选择LB - LL
;
问题转换:
对于任意区间x 其长度为LX, 令C( x, i)
表示 x区间与i区间的重叠区域的长度, 我们需要得到mi = min( C( x, ...))
, 然后用2 * ( LX - mi)
去更新答案;
.
核心在于求解mi
;
算法:
首先对所有区间按照pair
规则 排序;
此时对于任意区间x [xl, xr]
, 令区间t [tl, tr]
是和区间x重叠区域最小的区间 令mi
是他俩的重叠区域的长度, 分析t的情况: (令pre_rig
为 所有x前面的区间的r
端点的最小值, 令nex_lef
为 所有x后面的区间的l
端点的最大值);
1: t排序是在x的前面; , 则pre_rig
一定是t的右端点 (这需要贪心证明), 即mi = max( pre_rig - xl + 1, 0)
;
2: [[t排序是在x的后面]]&&[[tl > xr
]] (不相交), 则nex_lef
为t的左端点;
3: [[t排序是在x的后面]]&&[[tl <= xr && tr > xr
]] (即相交但不被x所包含); 则nex_lef
为t的左端点 (这需要贪心证明);
4: [[t排序是在x的后面]]&&[[tr <= xr
]] (即t被x完全包含);
.
这是我们讨论的核心 也是难点;
.
如果你用暴力, 即遍历他后面的所有区间j
则终止条件 不是jr > xr
, 而是jl > xr
; 举个例子 x: [1,3]; [2,5]; [3,3]
, 有一个完全被包含的区间[3,3]
, 但是 在[2,5]
时 就break
掉了, 这是不对的; 当然这会超时;
.
求解他 确实很难… 很需要思维跳跃… 直接给答案: 此时的t 一定是所有区间里 长度最小的;
.
证明: 假如有另一个区间m 其长度比t还小, 分析m的情况:
1: m排序在x的前面; 那么 m与x的重叠区域 一定小于mi
, 这与假设矛盾;
2: [[m排序在x的后面]]&&[[与x不相交]]; 因为0 < mi
, 这与假设矛盾;
3: [[m排序在x的后面]]&&[[与x相交但不被x包含]]; 因为m与x的重叠区域长度 < mi
, 这与假设矛盾;
因此, 只剩下最后一种情况, 即 m一定属于: [[m排序在x的后面]]&&[[被x包含]]的情况; 这种情况 重叠区域长度 就等于m区间长度;
.
当然, 你还要证明下: 即使答案不来自于m(即第4种情况) 你使用m来更新答案 是不会影响答案的;
.
.
简单证明下: (1: 要么m=x
此时重叠区域达到了最大值 即x的长度 这是最劣的情况 任何区间与x的重叠区域 一定是<= x长度
的;) (2: 否则 m != x
(m的长度 一定小于x) 此时我们以重叠区域为m来更新答案 而实际上 m区间 属于{1/2/3}情况 因此实际上m与x的重叠区域 一定是<= m的长度
);
mi_len = `所有区间长度的最小值`;
sort( A, A + N);
int pre_rig = 1e9, nex_lef = A[ N-1].first;
for( int i = 0; i < N; ++i){
auto l = A[i].first, r = A[i].second;
auto s = r - l + 1;
int intersect = s;
//> pre
Tools::Min_self( intersect, pre_rig - l + 1);
//> nex
Tools::Min_self( intersect, r - nex_lef + 1);
//> mid
Tools::Min_self( intersect, mi_len);
Tools::Max_self( intersect, 0);
Tools::Max_self( ANS, 2 * ( s - intersect));
Tools::Min_self( pre_rig, r);
}