题面传送门

首先一眼二分答案,我们假设距离 \((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. DataX的安装及使用

    DataX的安装及使用 目录 DataX的安装及使用 DataX的安装 DataX的使用 stream2stream 编写配置文件stream2stream.json 执行同步任务 执行结果 mysq ...

  2. Convolutional Neural Network-week1编程题(一步步搭建CNN模型)

    Convolutional Neural Networks: Step by Step implement convolutional (CONV) and pooling (POOL) layers ...

  3. BUAA2020软工作业(四)——结对项目

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 结对项目作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力,团队协作能力 这个作业在哪 ...

  4. MD支持程度测试

    Editor.md 目录 (Table of Contents) [TOCM] 目录 Editor.md Heading 1 Heading 2 Heading 3 Heading 4 Heading ...

  5. NOIP 模拟 $79\; \rm y$

    题解 \(by\;zj\varphi\) NOIP2013 的原题 最简单的思路就是一个 bfs,可以拿到 \(70pts\) 75pts #include<bits/stdc++.h> ...

  6. 【BZOJ2070】列队春游———[组合数学+概率DP]

    数学渣滓不可做の题OTZ Description (单身人士不可做 Input                     |            Output 3                   ...

  7. Ubuntu鼠标变十字 不能点击

    出现这种情况,应该是bash 直接运行了python文件 系统中出现了一个import 进程. python文件中除了注释应该是import在最前边 ps -ef|grep import 可以查看系统 ...

  8. 应对gitee容量超限. 保留star/fork/评论

    应对gitee容量超限 进入企业版,"管理"-"仓库管理",点"清空仓库". 在E:\gitee目录上右击,"git bash h ...

  9. vue+elementUI中单选框el-radio设置默认值和唯一标识某个单选框

    vue+elementUI中单选框el-radio设置默认值 如果后台返回的单选框的值是number:单选框的lable需要设置成 :lable='0';如下: <el-form-item la ...

  10. CommonJS与ES6 Module的使用与区别

    CommonJS与ES6 Module的使用与区别 1. CommonJS 1.1 导出 1.2 导入 2. ES6 Module 2.1 导出 2.2 导入 3. CommonJS 与 ES6 Mo ...