【题解】CF845E Fire in the city
提供一种的做法
温馨提示 : 本题细节极多
原题
题意:
有一个 的网格,有 个格子被点燃了,被点燃的格子会向八联通扩散,也就是说 单位时间后,切比雪夫距离 的格子都会被点燃。
你可以在开始时额外点燃一个格子,使得整个网格被点燃的时间最短。
整个网格被点燃,即“最晚被点燃的格子被点燃的的最小时间”,所以很明显考虑二分答案,二分时间,在坐标系中求出没有被覆盖的点的横纵坐标的极差的较大值,若不大于二倍的时间,则一定可以被这个新的点燃的格子覆盖。
本题的CF难度约在2500左右,一种做法是二分答案+离散化加差分,时间复杂度为 ,另一篇题解已经讲的很清楚了。
加强版与更优秀的时间复杂度
考虑这道题的加强版,,该怎么做。
和之前相同,我们考虑二分答案,将各点以时间扩展为矩形(正方形)。
发现这是平面上的一些正方形,求极差,即”轮廓“点,所以考虑扫描线。 (横纵皆可,本题采用横扫描线,个人习惯 表示第x行第y列)
同样地,先将四个顶点离散化,然后考虑如何维护未被覆盖的点的极差。
先考虑如何求y的极差:
对于每个有意义的x,我们需要求该横坐标未被覆盖的最小y,最大y即可。
和面积并的扫描线类似,采用一种不push_down的线段树维护,(区间覆盖型扫描线都是如此,因为后面一定会有一个对应都相同相反的点来更新它)
对于每个线段 ,维护三个值:
,表示该线段被整条覆盖的次数,同普通扫描线。
,表示该线段中最左边没有被覆盖的点的y,若不存在,则为
,表示给线段中最右边没有被覆盖的点的y,若不存在,则为
那么我们只用每次查询 即可求出当前x的最大y和最小y了
考虑如何维护push_up
如果该线段被覆盖,,那么 分别为 ,因为全线段被覆盖。
如果该线段未被直接完全覆盖,且不为叶子节点,则查询左儿子,右儿子的 分别取 即可。
若为叶子节点且未被覆盖,则最大,最小值均为该节点维护的区间(点)。
如下
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));
}
接下来考虑如何求上下极差,即
笨办法:旋转一遍坐标系,再跑一次,或跑一边纵扫描线(我真的这么做了)
Trick: 从上往下第一个 返回值的即为 ,最后一个为 。因为返回值为 仅当该行被完全覆盖。
最后同原题做法,横纵坐标的极差的较大值,若不大于二倍的时间,则一定可以被这个新的点燃的格子覆盖。
细节:
1.需要将大矩形的四个角离散化
2.在对于矩形 ,末尾修改不是 而是 因为同差分,应在矩形结束的后一行减去。
3.要将所有矩形的 也离散化,因为这些点也有可能成为未被覆盖的极值点。
- (3)中的离散化不要把0离散化了
5.线段树应开至少 12k 大小,因为每个矩形最多会产生3个可能被计算的点。
code
因为码量较大,篇幅有限,为了代码可读性不宜压行,故在此只放出线段树,扫描线,check部分,完整代码见下。
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,提高难度的扫描线大概就毕业了。
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970985,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步