题面传送门

首先一眼二分答案,我们假设距离 \((i,j)\) 最近的 scarefrog 离它的距离为 \(mn_{i,j}\),那么当我们二分到 \(mid\) 时我们显然只能经过 \(mn_{i,j}\ge mid\) 的点,如果我们已经知道了 \(mn_{i,j}\),那么检验 \(mid\) 是否可行就是一遍 BFS 能搞定的事,于是我们的目标就是求出 \(mn_{i,j}\) 即可。

故下面就有两种做法,一种是我的乱搞做法,一种是正经的做法。


乱搞做法

这就是某个奇怪的人自己发明出来的某个奇怪的基于某个奇怪的假做法的乱搞做法(

显然对于某个点 \((x,y)\) 而言,对它产生贡献的点可能在它的左上方、右上方、左下方、右下方(对于与 \((x,y)\) 在同一行或同一列的点,我们显然可以把它简单归约到 \((x,y)\) 的某一侧),显然四种情况是类似的,我们只需解决一种情况即可求出解决另外几种。考虑怎样求出左上角的答案,那么对于两个 scarefrog 所在的点 \((x_1,y_1),(x_2,y_2)\),如果 \(x_1<x_2,y_1<y_2\) 且 \((x_1,y_1),(x_2,y_2)\) 都在 \((x,y)\) 的左上角,那 \((x_1,y_1)\) 显然是不优的,因此我们维护一个队列满足随着 \(x\) 的增大,\(y\) 单调递减,表示决策点的集合。其次我们还可以发现,对于一个点 \(P(x,y)\) 和两个决策点 \(A(x_1,y_1),B(x_2,y_2)\),如果 \(PA>PB\),且 \(x_1>x_2\),那么如果我们将 \(P\) 再往右多移动几格,到 \(P(x,y+k)\),那么感性理解一下,\(PA\) 增加的长度 \(\Delta PA\) 大于 \(PB\) 增加的长度 \(\Delta PB\),因此还有 \(PA>PB\),此时 \(A\) 点就是没有用的了,因此我们考虑维护一个单调队列,随着 \(x\) 的增大,\(y\) 在不断减小,同时该点到点 \(P\) 的距离也在不断减小,那么我们只需取出队首的元素更新答案即可。

这个做法看起来非常正确,但非常抱歉,它是有漏洞的,因为当你将 \(P\) 从 \((x,y)\) 移至 \((x,y+1)\) 时,队列中的元素并不一定还满足到 \(P\) 点的距离单调。

不过这个做法在随机数据下表现非常优秀,它奇迹般地只 WA 了 \(3\) 个点,取得了 \(88\) 分的好成绩,于是我就灵机一动,将“取对首元素更新答案”改为“取队列前 \(30\) 个元素更新答案”,然后……它!过!了!

时间复杂度 \(\mathcal O(30nm+nm\log nm)\)

感觉这个做法非常不靠谱,欢迎鸽鸽们来叉

const int MAXN=1000;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int dis(int x1,int y1,int x2,int y2){
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
int n,wx,wy,sx,sy,ex,ey;
bool is[MAXN+5][MAXN+5];
int pos[MAXN+5],mn[MAXN+5][MAXN+5];
bool vis[MAXN+5][MAXN+5];
bool check(int lim){
if(mn[sx][sy]<lim||mn[ex][ey]<lim) return 0;
memset(vis,0,sizeof(vis));queue<pii> q;
q.push(mp(sx,sy));vis[sx][sy]=1;
while(!q.empty()){
pii p=q.front();q.pop();int x=p.fi,y=p.se;
for(int d=0;d<4;d++){
int nx=x+dx[d],ny=y+dy[d];
if(nx<1||nx>wx||ny<1||ny>wy) continue;
if(mn[nx][ny]<lim||vis[nx][ny]) continue;
vis[nx][ny]=1;q.push(mp(nx,ny));
}
} return vis[ex][ey];
}
int main(){
scanf("%d%d%d%d%d%d%d",&wx,&wy,&sx,&sy,&ex,&ey,&n);
for(int i=1,x,y;i<=n;i++) scanf("%d%d",&x,&y),is[x][y]=1;
memset(mn,63,sizeof(mn));
memset(pos,0,sizeof(pos));
for(int i=1;i<=wx;i++){
static pii q[MAXN+5];int hd=1,tl=0;
for(int j=1;j<=wy;j++){
if(is[i][j]) pos[j]=i;
if(pos[j]){
while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
q[++tl]=mp(pos[j],j);
} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
for(int l=hd;l<=min(hd+30,tl);l++)
chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
}
}
memset(pos,0,sizeof(pos));
for(int i=1;i<=wx;i++){
static pii q[MAXN+5];int hd=1,tl=0;
for(int j=wy;j;j--){
if(is[i][j]) pos[j]=i;
if(pos[j]){
while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
q[++tl]=mp(pos[j],j);
} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
for(int l=hd;l<=min(hd+30,tl);l++)
chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
}
}
memset(pos,0,sizeof(pos));
for(int i=wx;i;i--){
static pii q[MAXN+5];int hd=1,tl=0;
for(int j=1;j<=wy;j++){
if(is[i][j]) pos[j]=i;
if(pos[j]){
while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
q[++tl]=mp(pos[j],j);
} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
for(int l=hd;l<=min(hd+30,tl);l++)
chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
}
}
memset(pos,0,sizeof(pos));
for(int i=wx;i;i--){
static pii q[MAXN+5];int hd=1,tl=0;
for(int j=wy;j;j--){
if(is[i][j]) pos[j]=i;
if(pos[j]){
while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
q[++tl]=mp(pos[j],j);
} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
for(int l=hd;l<=min(hd+30,tl);l++)
chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
}
} int l=0,r=2e6,p=0;
// for(int i=1;i<=wx;i++) for(int j=1;j<=wy;j++) printf("%d%c",mn[i][j]," \n"[j==wy]);
while(l<=r){
int mid=l+r>>1;
if(check(mid)) p=mid,l=mid+1;
else r=mid-1;
} printf("%d\n",p);
return 0;
}

正经做法

还是考虑怎样求 \(mn_{i,j}\),我们记 \(f_{i,j}\) 表示与 \((i,j)\) 在同一列的点中,与 \((i,j)\) 距离最近的坏点的距离——那么显然 \(f_{i,j}\) 可以通过扫一遍整个 matrix 求出,时间复杂度 \(nm\)。

接下来考虑知道了 \(f_{i,j}\) 怎样求 \(mn\),那么我们就枚举离 \((i,j)\) 最近的坏点所在的列 \(k\),可得转移方程 \(mn_{i,j}=\min\limits_{k}\{f_{i,k}^2+(j-k)^2\}\),稍微变形一下即可得到 \(mn_{i,j}=j^2+\min\limits_{k}\{-2jk+f_{i,k}^2+k^2\}\),这显然就是一个个一次函数,如果我们记 \(l_k:y=-2kx+f_{i,k}^2+k^2\),那么求 \(mn_{i,j}\) 只需求出 \(\max\{l_k(j)\}\),可以用凸包/斜率优化/李超线段树维护,本人写的是李超线段树的求法,因为不容易爆精度。

const int MAXN=1000;
const int INF=0x3f3f3f3f;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int wx,wy,sx,sy,ex,ey,n;
bool is[MAXN+5][MAXN+5];
int mn[MAXN+5][MAXN+5],f[MAXN+5][MAXN+5];
struct line{
int k,b;
line(int _k=0,int _b=INF):k(_k),b(_b){}
int get(int x){return x*k+b;}
};
struct node{int l,r;line v;} s[MAXN*4+5];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].v=line();if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void modify(int k,line v){
int l=s[k].l,r=s[k].r,mid=l+r>>1;
int l1=s[k].v.get(l),r1=s[k].v.get(r),m1=s[k].v.get(mid);
int l2=v.get(l),r2=v.get(r),m2=v.get(mid);
if(l2>=l1&&r2>=r1) return;
if(l1>=l2&&r1>=r2) return s[k].v=v,void();
if(m2<=m1){
if(l2<=l1) modify(k<<1|1,s[k].v),s[k].v=v;
else modify(k<<1,s[k].v),s[k].v=v;
} else {
if(l2<=l1) modify(k<<1,v);
else modify(k<<1|1,v);
}
}
int query(int k,int x){
if(s[k].l==s[k].r) return s[k].v.get(x);int mid=s[k].l+s[k].r>>1;
return min(((x<=mid)?query(k<<1,x):query(k<<1|1,x)),s[k].v.get(x));
}
bool vis[MAXN+5][MAXN+5];
bool check(int lim){
if(mn[sx][sy]<lim||mn[ex][ey]<lim) return 0;
memset(vis,0,sizeof(vis));queue<pii> q;
q.push(mp(sx,sy));vis[sx][sy]=1;
while(!q.empty()){
pii p=q.front();q.pop();int x=p.fi,y=p.se;
for(int d=0;d<4;d++){
int nx=x+dx[d],ny=y+dy[d];
if(nx<1||nx>wx||ny<1||ny>wy) continue;
if(mn[nx][ny]<lim||vis[nx][ny]) continue;
vis[nx][ny]=1;q.push(mp(nx,ny));
}
} return vis[ex][ey];
}
int main(){
scanf("%d%d%d%d%d%d%d",&wx,&wy,&sx,&sy,&ex,&ey,&n);
for(int i=1,x,y;i<=n;i++) scanf("%d%d",&x,&y),is[x][y]=1;
memset(f,63,sizeof(f));
for(int j=1;j<=wy;j++){
int pre=-INF;
for(int i=1;i<=wx;i++){
if(is[i][j]) pre=i;
chkmin(f[i][j],i-pre);
} pre=INF;
for(int i=wx;i;i--){
if(is[i][j]) pre=i;
chkmin(f[i][j],pre-i);
}
}
for(int i=1;i<=wx;i++){
build(1,1,wy);
for(int j=1;j<=wy;j++) if(f[i][j]<1e9)
modify(1,line(-2*j,j*j+f[i][j]*f[i][j]));
for(int j=1;j<=wy;j++) mn[i][j]=query(1,j)+j*j;
} int l=0,r=2e6,p=0;
// for(int i=1;i<=wx;i++) for(int j=1;j<=wy;j++) printf("%d%c",mn[i][j]," \n"[j==wy]);
while(l<=r){
int mid=l+r>>1;
if(check(mid)) p=mid,l=mid+1;
else r=mid-1;
} printf("%d\n",p);
return 0;
}

洛谷 P3438 - [POI2006]ZAB-Frogs(乱搞/李超线段树)的更多相关文章

  1. 「洛谷4197」「BZOJ3545」peak【线段树合并】

    题目链接 [洛谷] [BZOJ]没有权限号嘤嘤嘤.题号:3545 题解 窝不会克鲁斯卡尔重构树怎么办??? 可以离线乱搞. 我们将所有的操作全都存下来. 为了解决小于等于\(x\)的操作,那么我们按照 ...

  2. 「洛谷3870」「TJOI2009」开关【线段树】

    题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...

  3. 【洛谷5251】[LnOI2019] 第二代图灵机(线段树+ODT)

    点此看题面 大致题意: 有单点修改数字和区间着色两种修改操作,询问你某段区间内包含所有颜色且数字和最小的子区间的数字和,或某段区间内没有重复颜色且数字和最大的子区间的数字和.数据随机. \(ODT\) ...

  4. 洛谷P5279 [ZJOI2019]麻将(乱搞+概率期望)

    题面 传送门 题解 看着题解里一堆巨巨熟练地用着专业用语本萌新表示啥都看不懂啊--顺便\(orz\)余奶奶 我们先考虑给你一堆牌,如何判断能否胡牌 我们按花色大小排序,设\(dp_{0/1,i,j,k ...

  5. 洛谷P3120 [USACO15FEB]牛跳房子(动态开节点线段树)

    题意 题目链接 Sol \(f[i][j]\)表示前\(i\)行\(j\)列的贡献,转移的时候枚举从哪里转移而来,复杂度\(O(n^4)\) 然后考虑每一行的贡献,动态开节点线段树维护一下每种颜色的答 ...

  6. 洛谷P3605 [USACO17JAN] Promotion Counting 晋升者计数 [线段树合并]

    题目传送门 Promotion Counting 题目描述 The cows have once again tried to form a startup company, failing to r ...

  7. 洛谷P4556 [Vani有约会]雨天的尾巴(线段树合并)

    题目背景 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地 ...

  8. 【洛谷】P3919 【模板】可持久化线段树(主席树)

    题目 传送门:QWQ 分析 主席树的模板,囤着 代码 #include <bits/stdc++.h> using namespace std; ; ], rs[N*], root[N*] ...

  9. 洛谷P3178 [HAOI2015]树上操作(dfs序+线段树)

    P3178 [HAOI2015]树上操作 题目链接:https://www.luogu.org/problemnew/show/P3178 题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边 ...

随机推荐

  1. 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析

    本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...

  2. 对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)

    前言 这是<Spring Cloud 进阶>专栏的第六篇文章,往期文章如下: 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强? openFeign夺命连环9问,这谁受得了? 阿里面 ...

  3. 2020BUAA软工热身作业

    2020BUAA软工热身作业 17373010 杜博玮 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 热身作业 我在这个课程的目标是 学习软件工 ...

  4. zip和flatMap没有生效

    在Reactor 中flatMap和zip等没有生效 1.一个简单的示例代码如下: 2.示例运行结果 3.得到结论 最近在项目中使用了 Project Reactor ,但发现代码在写着写着有些地方没 ...

  5. 生产环境部署springcloud微服务启动慢的问题排查

    今天带来一个真实案例,虽然不是什么故障,但是希望对大家有所帮助. 一.问题现象: 生产环境部署springcloud应用,服务部署之后,有时候需要10几分钟才能启动成功,在开发测试环境则没有这个问题. ...

  6. 六步教你如何用PADS进行PCB设计?

    在使用PADS进行PCB设计的过程中,需要对印制板的设计流程以及相关的注意事项进行重点关注,这样才能更好的为工作组中的设计人员提供系统的设计规范,同时也方便设计人员之间进行相互的交流和检查. 02 设 ...

  7. sql server 如何跟更新拼接的数据(cast用法)

    我们在实际中会做如下图的连接 执行以后这个连接就会报错了,如下图所示   然后我们用cast将数字转换为字符串在连接,如下图所示     这次连接的结果就没问题了,如下图所示     最后如果两个数字 ...

  8. 转载: XILINX GT的基本概念

    https://zhuanlan.zhihu.com/p/46052855 本来写了一篇关于高速收发器的初步调试方案的介绍,给出一些遇到问题时初步的调试建议.但是发现其中涉及到很多概念.逐一解释会导致 ...

  9. cf12E Start of the season(构造,,,)

    题意: 给一个偶数N. 构造出一个矩阵. 满足:主对角线上全为0.每一行是0~N-1的一个全排列.矩阵关于主对角线对称. 思路: 觉得是智商题,,,,看完题解后觉得不难,但是我就是没想出来.只想到了前 ...

  10. 如何选择普通索引和唯一索引《死磕MySQL系列 五》

    系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强 ...