【题解】CF845E Fire in the city

提供一种$O(k \log k \log w)$的做法


温馨提示 : 本题细节极多


$\text{Part 1.}$ 原题

题意:

有一个 $n*m (n,m\leq 10^9)$ 的网格,有 $k(\leq 500)$ 个格子被点燃了,被点燃的格子会向八联通扩散,也就是说 $t$ 单位时间后,切比雪夫距离 $\max\{|x-x_1|,|y-y1|\} \leq t$ 的格子都会被点燃。

你可以在开始时额外点燃一个格子,使得整个网格被点燃的时间最短。

整个网格被点燃,即“最晚被点燃的格子被点燃的的最小时间”,所以很明显考虑二分答案,二分时间,在坐标系中求出没有被覆盖的点的横纵坐标的极差的较大值,若不大于二倍的时间,则一定可以被这个新的点燃的格子覆盖。

本题的CF难度约在2500左右,一种做法是二分答案+离散化加差分,时间复杂度为 $O(k^2 \log w)$,另一篇题解已经讲的很清楚了。


$\text{Part 2.}$ 加强版与更优秀的时间复杂度

考虑这道题的加强版,$k\leq 10^5,n,m\leq10^{18}$,该怎么做。

和之前相同,我们考虑二分答案,将各点以时间扩展为矩形(正方形)。

发现这是平面上的一些正方形,求极差,即”轮廓“点,所以考虑扫描线。 (横纵皆可,本题采用横扫描线,个人习惯 $(x,y)$ 表示第x行第y列)

同样地,先将四个顶点离散化,然后考虑如何维护未被覆盖的点的极差。

先考虑如何求y的极差:

对于每个有意义的x,我们需要求该横坐标未被覆盖的最小y,最大y即可。

和面积并的扫描线类似,采用一种不push_down的线段树维护,(区间覆盖型扫描线都是如此,因为后面一定会有一个对应$l,r$都相同$c$相反的点来更新它)

对于每个线段 $[l,r]$ ,维护三个值:

$\text 1.cnt[l,r]$,表示该线段被整条覆盖的次数,同普通扫描线。

$\text 2.mn[l,r]$,表示该线段中最左边没有被覆盖的点的y,若不存在,则为$+\infty$

$\text 3.mx[l,r]$,表示给线段中最右边没有被覆盖的点的y,若不存在,则为$-\infty$

那么我们只用每次查询 $mn[1,n],mx[1,n]$ 即可求出当前x的最大y和最小y了

考虑如何维护push_up

如果该线段被覆盖,$C[l,r] \neq 0$,那么 $mn[l,r],mx[l,r]$ 分别为$\pm\infty$ ,因为全线段被覆盖。

如果该线段未被直接完全覆盖,且不为叶子节点,则查询左儿子,右儿子的 $mn,mx$ 分别取 $\min,\max$ 即可。

若为叶子节点且未被覆盖,则最大,最小值均为该节点维护的区间(点)。

如下

void push_up(int x)
{
    if(C(x)) {MN(x) = 1e12+2,MX(x) = -1e12-2;return ;}
    if(L==R){ MN(x) = MX(x) = L; return  ;}
    MN(x) = min(MN(ls),MN(rs));
    MX(x) = max(MX(ls),MX(rs));
}

接下来考虑如何求上下极差,即 $\max x,\min x$

笨办法:旋转一遍坐标系,再跑一次,或跑一边纵扫描线(我真的这么做了)

Trick: 从上往下第一个 $mx[1,1]\neq-\infty$ 返回值的即为 $xmin$ ,最后一个为 $xmax$ 。因为返回值为 $-\infty$ 仅当该行被完全覆盖。

最后同原题做法,横纵坐标的极差的较大值,若不大于二倍的时间,则一定可以被这个新的点燃的格子覆盖。


$\text{Part.3}$ 细节:

1.需要将大矩形的四个角离散化

2.在对于矩形 $(x_1,y_1,x_2,y_2)$,末尾修改不是 $(x_2,y_1,y_2,-1)$ 而是$(x_2+1,y_1,y_2,-1)$ 因为同差分,应在矩形结束的后一行减去。

3.要将所有矩形的 $x_1-1$ 也离散化,因为这些点也有可能成为未被覆盖的极值点。

  1. (3)中的离散化不要把0离散化了

5.线段树应开至少 12k 大小,因为每个矩形最多会产生3个可能被计算的点。


$\text {Part.4}$ code

因为码量较大,篇幅有限,为了代码可读性不宜压行,故在此只放出线段树,扫描线,check部分,完整代码见下

完整代码

AC记录

struct Fire{
    int x,y;
} f[N];
struct node{
    int  t , l , r  ;
    int c ;
    bool operator<(const node &n1) const{
        return t<n1.t ;
    }
} q[N];
#define ls (x<<1)
#define rs (x<<1|1)
#define R Tree[x].r
#define L Tree[x].l
#define mid ((L+R)>>1)
#define C(x) Tree[x].cnt
#define MX(x) Tree[x].mx
#define MN(x) Tree[x].mn
int siz,tot;
int xl,xr,yl,yr;
vector<int> vx,vy ;
struct segement{
    int mn,mx,l,r,cnt;
    void init()
    {
        mn = l , mx = r ;
        cnt = 0;
    }
    void init2()
    {
        cnt = 0;
        l = -1;
        r = -1;
        mn = 1e12+2;
        mx = -1e12+2;
    }
    void upd(int c)
    {
        cnt+=c;
        if(cnt!=0) {mn = 1e12+2; mx = -1e12-2;} 
        return ;
    } 
}Tree[N<<4];
void push_up(int x)
{
    if(C(x)) {MN(x) = 1e12+2,MX(x) = -1e12-2;return ;}
    if(L==R){ MN(x) = MX(x) = L; return  ;}
    MN(x) = min(MN(ls),MN(rs));
    MX(x) = max(MX(ls),MX(rs));
    return  ; 
}
void build(int x,int l,int r)
{
    L=l;
    R=r;
    Tree[x].init();
    if(L==R) {return ;}
    build(ls,l,mid);
    build(rs,mid+1,r);
    push_up(x);    
}
void update(int x,int l,int r,int c)
{
    if(l<=L&&R<=r) {Tree[x].upd(c); push_up(x); return ;}
    if(l<=mid) update(ls,l,r,c);
    if(r>mid) update(rs,l,r,c);
    push_up(x);
}

int check(int tim)
{
    vx.clear();
    vy.clear();
    siz = 0;
    rep(i,1,4*N-1)
    Tree[i].init2();
    vx.psb(1);
    vx.psb(n);
    vx.psb(n+1);
    vy.psb(1);
    vy.psb(m);
    vy.psb(m+1);
    rep(i,1,k)
    {
        xl = max(f[i].x-tim , 1ll) ;
        xr = min(f[i].x+tim , n) ;
        yl = max(f[i].y-tim , 1ll) ;
        yr = min(f[i].y+tim , m) ;

        if(xl!=1)
        vx.psb(xl-1);
        vx.psb(xl);
        vx.psb(xr+1);

        vy.psb(yl);
        vy.psb(yr);
        vy.psb(yr+1);

        q[++siz].t = xl ;
        q[siz].l = yl;
        q[siz].r = yr;
        q[siz].c = 1;

        q[++siz].t = xr + 1;
        q[siz].l = yl ;
        q[siz].r = yr ;
        q[siz].c = -1;
    }
    ss(vx.begin() , vx.end());
    ss(vy.begin() , vy.end());
    ss(q+1,q+siz+1);
    qc(vx);
    qc(vy);
    vy.pop_back();

    build(1,0,(int) vy.size() - 1);

    int nw = 1,xmin = 1e12 , xmax = -1e12 ,  ymin = 1e12 , ymax =   -1e12 ;
    for(int i=0;i<(int) vx.size();++i)
    {
        if(vx[i]==n+1) break;
        while(q[nw].t==vx[i])
        {
            update(1,Findv(vy,q[nw].l),Findv(vy,q[nw].r),q[nw].c);
            ++nw;
        }
        if(MX(1)>-10) {upmin(xmin,vx[i]); upmax (xmax,vx[i]) ;}
        if(MX(1)>-10) upmax(ymax ,vy[MX(1)]  );
        if(MN(1)<=1e10) upmin(ymin,vy[MN(1)] );
    }
    return ((ymax-ymin<=tim*2&&xmax-xmin<=tim*2)?1:0);
}

“加强版”的难度大概在2900, 3000左右,需要对线段树有着较深的理解,且细节极多,若能独立AC,提高难度的扫描线大概就毕业了。

posted @ 2022-01-07 21:37  寂静的海底  阅读(3)  评论(0编辑  收藏  举报  来源