[CF1034D]Intervals of Intervals

\[\newcommand\size[1]{{\Big|}#1{\Big|}} \]

壹、题目描述 ¶

传送门 to Luogu.

贰、题解 ¶

下文将 区间的区间 称为 区区间,用字母表示,第 \(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\),即判断

\[\size{U(L+1,R)}\overset{?}{\ge} x \]

如果可以,就移动 \(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\),而对于区区间并集的求法,实际上也很经典,要有印象。

posted @ 2021-07-15 22:27  Arextre  阅读(50)  评论(0编辑  收藏  举报