[CF1034D]Intervals of Intervals
壹、题目描述 ¶
贰、题解 ¶
下文将 区间的区间 称为 区区间,用字母表示,第 \(i\) 个区间就是 \(I_i\),区区间 \([L,R]\) 就是 \(U(L,R)\),区区间的价值就是 \(\size{U(L,R)}\).
找 \(k\) 个不同区间使价值最大,我们显然找最大的 \(k\) 个区间嘛。
但是我们怎么知道这 \(k\) 个区区间是哪些?我们可以二分!具体二分第 \(k\) 大的区区间价值为 \(x\),然后检查价值大于等于 \(x\) 的区区间有多少,再和 \(k\) 进行比较。
那么问题来到了如何寻找价值大于等于 \(x\) 的区区间个数,这个时候我们需要进行一些观察。
§ Observation#1 §
我们考虑依次枚举区区间的右端点,设其为 \(R\),考虑将 \(L\) 从 \(R\) 开始依次向左移动,那么,如果某个时刻 \([L,R]\) 的区区间价值达到 \(x\) 了,那么对于 \(\le L\) 的部分也是合法的了,具体来说,从 \(R\) 开始向左,区间的并集只会增加不会减少。
对于一个 \(R\) 的最大的合法的 \(L\),我们称其为左极。
§ Observation#2 §
当 \(R\leftarrow R+1\) 时,它的左极只有可能 不动 或者在原来的基础上 向右移动,没有可能向左移动。
经过两次观察,发现左右端点实际上很像 \(\rm two-pointer\),显然,如果我们能够快速地求出某个区区间的并集大小,就有可能实现这个 \(\rm two-pointer\),那么现在我们面临的问题就是 —— 如果求一个区区间的并集大小。
对于这个问题,有一种经典做法(然而我并不知道),如下:
考虑按照编号从小到大加入区间,当加入的是第 \(i\) 个区间时,我们将 \([l_i,r_i]\) 的数都赋值为 \(i\),对于区区间 \([L,i]\) 的并集大小,就是数列中数值 \(\ge L\) 的数的个数。
更具体地,当我们加入区间 \(i\) 时,如果覆盖了区间 \(j\) 的部分或者整体,那么,对于 \(U(L,R)(L\in[j+1,i])\) 的并集大小都会整体增加 \(\size{I_i\cap I_j}\),而如果覆盖了一个原来没有值的区间,那么对于 \(L\in [1,i]\) 都会整体增加 \(r_i-l_i+1\).
对于实现方法,由于一次加入一个区间,对于被它完全覆盖的区间我们会将其删掉,对于有交集的区间,我们会将那些区间分裂,而这样的区间最多两个,所以我们加入一个区间,最多将区间数变多 \(2\),我们完全可以使用一个 \(\rm set\) 对现在有的区间进行维护,加入新区间时看是否有需要分裂的区间,然后将完全覆盖的区间删去,处理覆盖时顺便处理对于 \(\size{U}\) 的修改即可。
如何将这个方法应用到 \(\rm two-pointer\) 上?移动 \(R\) 时,我们可以将 \(I_R\) 加入,然后我们就得到了所有 \(U(L,R)\) 的值,然后我们就看能否移动 \(L\),即判断
如果可以,就移动 \(L\).
不难发现,维护 \(\size{U(L,R)}\) 需要使用线段树与 \(\rm set\),外层还需要二分,所以总复杂度是大概为 \(\mathcal O(n\log n\log 1e9)\),当 \(n\) 取 \(3\times 10^5\) 时,时间复杂度为 \(\mathcal O(3e5\times 18.19\times 29.89)\approx \mathcal O(171000000)\),卡卡能过,但是这并没有考虑常数。
但是这还能继续优化,由于我们每次移动 \(R\) 都是固定的,即,对于线段树的修改,操作都是固定的,所以我们没有必要每一次二分出 \(x\) 使用 \(\rm two-pointer\) 检查时都做一遍,我们可以最开始模拟一下,然后处理出对于每一个 \(R\) 我们将会进行的修改操作,并且由于我们是按照顺序移动端点,所以我们可以使用差分数组替换线段树,这样复杂度就成功降低至 \(\mathcal O(n\log 1e9)\) 了。
对于最后计算答案,实际上实现方式十分相似,自己意会一下罢。
叁、参考代码 ¶
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
// #define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=3e5;
typedef pair<pii, int> Interval;
typedef set< pair<pii, int> >::iterator Shit;
#define saya(a, b, c) mp(mp(a, b), c)
pii seg[maxn+5];
int n; ll K;
/**
* @param index the right endpos
* @param fi the left endpos
* @param se delta
*/
vector<pii>cmd[maxn+5];
set<Interval>s;
inline void add(Interval x){
/** split the crossing interval first*/
Shit it=s.lower_bound(saya(x.fi.fi, 0, 0)); Interval tmp;
if(it!=s.begin()){
--it;
if(x.fi.fi < it->fi.se){ // pay attention to this constraint
tmp=*it; s.erase(it);
s.insert(saya(tmp.fi.fi, x.fi.fi, tmp.se));
s.insert(saya(x.fi.fi, tmp.fi.se, tmp.se));
}
}
it=s.lower_bound(saya(x.fi.se, 0, 0));
if(it!=s.begin()){
--it;
if(x.fi.se < it->fi.se){ // pay attention to this constraint
tmp=*it; s.erase(it);
s.insert(saya(tmp.fi.fi, x.fi.se, tmp.se));
s.insert(saya(x.fi.se, tmp.fi.se, tmp.se));
}
}
/** get @p cmd[x.se] */
Shit op=s.lower_bound(saya(x.fi.fi, 0, 0)), ed=op;
int pre=x.fi.fi;
if(op != s.end() && op->fi.se <= x.fi.se){
do{
int L=ed->fi.fi, R=ed->fi.se;
if(pre < L) cmd[x.se].push_back(mp(1, L-pre));
cmd[x.se].push_back(mp(ed->se+1, R-L));
pre=R, ++ed;
}while(ed != s.end() && ed->fi.se <= x.fi.se);
if(pre < x.fi.se) cmd[x.se].push_back(mp(1, x.fi.se-pre));
s.erase(op, ed);
}
else cmd[x.se].push_back(mp(1, x.fi.se-x.fi.fi));
s.insert(x); /** don't forget! */
}
inline void input(){
n=readin(1), K=readin(1);
rep(i, 1, n){
seg[i].fi=readin(1), seg[i].se=readin(1);
add(mp(seg[i], i));
}
}
/** @brief delta array */
ll f[maxn+5];
inline ll counter(int x){
rep(i, 0, n) f[i]=0; // should clear position 0
int l=0; ll ret=0;
rep(i, 1, n){
for(pii modi: cmd[i]){
int p=modi.fi, delta=modi.se;
p=max(p, l);
f[p]+=delta, f[i+1]-=delta;
}
/** try to move the left endpos */
while(l+1 <= n && f[l+1]+f[l] >= x){
f[l+1]+=f[l]; ++l;
}
ret+=l;
}
return ret;
}
/** @brief a little bit similar to @p counter() */
inline ll getans(int x){
rep(i, 0, n) f[i]=0; // should clear position 0
int l=0; ll ret=0, cur=0;
rep(i, 1, n){
for(pii modi: cmd[i]){
int p=modi.fi, delta=modi.se;
if(p <= l) cur+=1ll*delta*(l-p+1);
p=max(p, l);
f[p]+=delta, f[i+1]-=delta;
}
while(l+1 <= n && f[l+1]+f[l] >= x){
f[l+1]+=f[l];
cur+=f[l+1];
++l;
}
ret+=cur;
}
return ret;
}
inline int bisearch(){
int l=1, r=1e9, mid, ans=-1;
ll ret;
while(l <= r){
mid=(l+r)>>1;
ret=counter(mid);
if(ret < K) r=mid-1;
else ans=mid, l=mid+1;
}
return ans;
}
signed main(){
input();
ll ret=bisearch();
// printf("ret == %d\n", ret);
writc(getans(ret+1)-1ll*ret*(counter(ret+1)-K));
return 0;
}
肆、关键之处 ¶
两个观察最重要!它引出了我们解题的重心 —— \(\rm two-pointer\),而对于区区间并集的求法,实际上也很经典,要有印象。