【题解CF1641C】整体二分做法

提出一种不同的做法。

考虑每一条线段最多确定一个病人,即该线段中只剩一个点没有被确定为非病人,其他点都被确认为非病人时,这个点就会被确定为病人。

题目只会给出合法的数据

也就是说,我们需要知道每个“含有病人的区间”最早在什么时候被确定为有病人。

每个点最早会在某个时候被确定为有病人or没病人,没病人很简单,线段树从后往前区间覆盖即可。

由于这样的线段数量太多了,于是考虑把“询问”拿出来,把区间$[l,r]$有病人视作“询问”,二分被确定为是病人的时间即可;

然后发现要支持以下操作:

  • 区间覆盖
  • 查询区间内没有被覆盖的大小并返回任意没有被覆盖的点
  • 撤销区间覆盖

这不就是扫描线吗?不$\text{push\_down}$即可。

线段树的$\text {push\_up}$


 void push_up(int x)
    {
           if(L==R) {
           if(T(x)) S(x) = 0 , V(x) = 0;
           else S(x) = L , V(x) = 1;
           return ;
        }
        if(T(x)){ S(x) = 0 ; V(x) = 0 ; return ;}
        V(x) = V(ls) + V(rs) ;
        if(S(ls)) S(x) = S(ls) ;
        else if(S(rs)) S(x) = S(rs) ;
        else S(x) = 0 ;
    }

非常类似扫描线中的覆盖树

不会扫描线建议看这篇

$T(x)$维护整个区间的覆盖次数。

$G(x)$返回任意一个未被覆盖点

$V(x)$返回区间内没有覆盖的长度

然后整体二分就是一般的整体二分,可以先二分$[mid+1,r]$再撤回操作再二分$[l,mid]$。

整体二分部分:

(其实我写复杂了很多,在确定答案时不用再进行/撤回操作)

void solve(int L,int R,int l,int r)
{
    //确定了询问[l,r]的答案在[L,R]之间
    if(R<L||r<l) return ;
    if(L==R) {
        rep(i,l,r) 
        if(q[i].k == 0) 
        {
            scanline.cover(1,q[i].l,q[i].r) ;//这一部分是不需要的,因为答案一定合法
        }
        rep(i,l,r) 
        {
            if(q[i].k == 1)
            {
                auto p = scanline.query(q[i].l,q[i].r) ;
                if(p.first == 1) {fsto[p.second] = min(fsto[p.second] , max(L , q[i].tim) ) ; //取min是因为这条线段有可能还没有给出,区间就已经只剩一个没有被覆盖的点了。
                }
            }        
        }
        rep(i,l,r)
        if(q[i].k == 0)
          scanline.discover(1,q[i].l,q[i].r) ;//取消覆盖
        return ;
    }
    int ls = 0 , rs = 0;
    int mid = L+R>>1 ;
    rep(i,l,r) 
    {
        if(q[i].k == 0)
        {
            if(q[i].tim <= mid) {
            scanline.cover(1,q[i].l,q[i].r),lft[++ls] = q[i];}
            else rig[++rs] = q[i] ;             
        }
    }
    rep(i,l,r) 
    {
        if(q[i].k == 1) 
        {
            auto p = scanline.query(q[i].l,q[i].r) ; 
            if(p.first > 1) rig[++rs] = q[i];//整体二分,若覆盖区间大于等于1则尝试更后的时间,否则尝试更前的时间
            else lft[++ls] = q[i] ;  
        }
    }
    rep(i,l,l+ls-1) q[i] = lft[i - l + 1] ;
    rep(i,l+ls,r) q[i] = rig[i - l - ls + 1] ;
    solve(mid+1,R,l+ls,r);
    rep(i,l,r) 
    {
        if(q[i].k == 0)
        {
            if(q[i].tim <= mid) 
            scanline.discover(1,q[i].l,q[i].r) ;           
        }
    }//撤销覆盖
    solve(L,mid,l,l+ls-1) ;
}

时间复杂度为$O(n \log n + q \log n \log q)$ 常数偏大,一个优化是把我那堆完全没有必要的在确认答案之前的覆盖于删除去掉。

由于个人代码习惯,代码较长,不易在此放出,代码与评测记录在这。

完整的代码

评测记录

posted @ 2022-02-26 17:43  寂静的海底  阅读(4)  评论(0编辑  收藏  举报  来源