题目

题目大意

给你一个有\(n\)个点的平面。

选择三个点,求两两之间曼哈顿距离和的最大值和最小值。


思考历程&正解

比赛的时候没有想太多,但感觉似乎比较水……

首先有个很显然的性质,答案为这三个点的最大最小横坐标之差和最大最小纵坐标之差的和。

可以把它看成矩形的周长,容易发现矩形至少一个顶点是三个点之一。

后来才发现水的是求最大值,而不是求最小值。

比赛之后开始和WMY刚……

最大值是很好求的。我一开始打了个线段树来求,后来发现根本不用……

求出所有点的\(Xmin,Xmax,Ymin,Ymax\),然后\(O(n)\)扫一遍,贪心一下就没了。

主要的问题在于最小值。

显然有两种情况:三个点都在矩形边上;有一个点在矩形内部。

第二种情况很好求,枚举中间的点,线段树维护右上和左下(或右下和左上)的最近的点。

主要问题是第一种情况。

假设我们现在已经将点按照\(x\)坐标为第一关键字排序,以\(y\)坐标为第二关键字排序,都是从小到大。

枚举矩形左下的节点(至于其它方向的节点,直接旋转或对称就行了)。



(很简陋的一张图)

左下的节点(记作\(A\))固定了\(Xmin\)和\(Ymin\),右边的节点(记作\(B\))固定了\(Xmax\),上边的节点(记作\(C\))固定了\(Ymax\)

由于\(A\)是固定的,所以我们值只需要让\(Xmax+Ymax\)最小。

考虑平行于\(y\)轴的扫描线,从右到左。

我们需要将\(B\)点表示的\(Xmax\)挂在\(C\)点上,计算\(A\)点答案的时候要查询到\(C\)点。

维护最小的\(Ymax+Xmax\),挂在行上。

扫描线扫到\(C\)的时候,就将\(C.y\)这一行的\(Xmax\)更新一下。

扫描线扫到\(B\)的时候,就将\([0,B.y)\)的\(Ymax\)更新一下。

扫描线扫到\(A\)的时候,是询问\([A.y,10^8]\)的最小\(Xmax+Ymax\)

(在实际操作的时候,每个点都需要进行三个操作,因为它有可能是\(A\)点、\(B\)点、\(C\)点)

接下来考虑如何维护这个东西。

线段树上每个节点维护\(ans\)、\(MIN_{Xmax}、tag\)

既然\(Ymax\)是区间修改,肯定是要打\(tag\)标记的。

在打\(tag\)标记的时候(以及下传的时候),若\(MIN_{Xmax}+tag<ans\),则更新\(ans\)

若这个点原先就有个\(tag\),就跟这个\(tag\)比较大小,若更小,则替代它。

对于\(Xmax\)的单点修改,直接在线段树中找到对应的点(找的时候标记要下传,到了最底端的时候标记清空,这就使得这个节点及它的祖先都没有标记),修改它的\(MIN_{Xmax}\)(但是\(ans\)是不用改的,因为\(Ymax\)是在\(Xmax\)后面出现的,现在这个新的\(Xmax\)暂时是最后一个,没有匹配的\(Ymax\))

然后这道题就被解决了。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define MAX 100000000
#define INF 1000000000
int n;
struct DOT{
int x,y;
int num;
} d[N];
inline void rever(int a,int b){//把所有点进行对称操作
for (int i=1;i<=n;++i)
d[i]={a==1?d[i].x:MAX-d[i].x,b==1?d[i].y:MAX-d[i].y,d[i].num};
}
inline bool cmpd(const DOT &a,const DOT &b){
return a.x<b.x || a.x==b.x && a.y<b.y;
}
int ans1=-INF,ans2=INF;
struct Node1{
Node1 *lc,*rc;
int val;
} seg1[N*30],*null1,*root1;
int cnt1;
inline Node1 *newnode1(){return &(seg1[++cnt1]={null1,null1,-INF});}
void change1(Node1 *t,int l,int r,int x,int c){
if (l==r){
t->val=max(t->val,c);
return;
}
int mid=l+r>>1;
if (x<=mid)
change1(t->lc==null1?t->lc=newnode1():t->lc,l,mid,x,c);
else
change1(t->rc==null1?t->rc=newnode1():t->rc,mid+1,r,x,c);
t->val=max(t->lc->val,t->rc->val);
}
int query1(Node1 *t,int l,int r,int st,int en){
if (t==null1)
return -INF;
if (st<=l && r<=en)
return t->val;
int mid=l+r>>1,res=-INF;
if (st<=mid)
res=max(res,query1(t->lc,l,mid,st,en));
if (mid<en)
res=max(res,query1(t->rc,mid+1,r,st,en));
return res;
}
struct Node2 *null2;
struct Node2{
Node2 *lc,*rc;
int ans,minx,tag;
inline void gmin(int y){
if (this==null2)
return;
if (y<tag){
tag=y;
if (minx+y<ans)
ans=minx+y;
}
}
inline void pushdown(){
if (tag<INF){
lc->gmin(tag);
rc->gmin(tag);
tag=INF;
}
}
inline void update(){
minx=min(lc->minx,rc->minx);
ans=min(lc->ans,rc->ans);
}
} seg2[N*30],*root2;
int cnt2;
inline Node2 *newnode2(){return &(seg2[++cnt2]={null2,null2,INF,INF,INF});}
void changex(Node2 *t,int l,int r,int x,int c){
if (l==r){
t->minx=c;
t->tag=INF;
return;
}
t->pushdown();
int mid=l+r>>1;
if (x<=mid)
changex(t->lc==null2?t->lc=newnode2():t->lc,l,mid,x,c);
else
changex(t->rc==null2?t->rc=newnode2():t->rc,mid+1,r,x,c);
t->update();
}
void changey(Node2 *t,int l,int r,int st,int en,int c){
if (st<=l && r<=en){
t->gmin(c);
return;
}
t->pushdown();
int mid=l+r>>1;
if (st<=mid)
changey(t->lc==null2?t->lc=newnode2():t->lc,l,mid,st,en,c);
if (mid<en)
changey(t->rc==null2?t->rc=newnode2():t->rc,mid+1,r,st,en,c);
t->update();
}
int query2(Node2 *t,int l,int r,int st,int en){
if (t==null2)
return INF;
if (st<=l && r<=en)
return t->ans;
t->pushdown();
int mid=l+r>>1,res=INF;
if (st<=mid)
res=min(res,query2(t->lc,l,mid,st,en));
if (mid<en)
res=min(res,query2(t->rc,mid+1,r,st,en));
return res;
}
int minxy[4][N];//用来记录某次状况下右上角的最近点
inline void work(int flag){
sort(d+1,d+n+1,cmpd);
if (flag<2){
//这是第二种情况的处理。不能在flag=2或3的时候处理一次,因为那时候不能保证顺序与这次完全相反
//如果顺序没有完全相反,两个点重合时候可能会当作三个点来看。
null1=seg1;
*null1={null1,null1,-INF};
cnt1=0;
root1=newnode1();
for (int i=1;i<=n;++i){
int tmp=query1(root1,0,MAX,0,d[i].y);
minxy[flag][d[i].num]=min(d[i].x+d[i].y-tmp,INF);
change1(root1,0,MAX,d[i].y,d[i].x+d[i].y);
}
null1=seg1;
*null1={null1,null1,-INF};
cnt1=0;
root1=newnode1();
for (int i=n;i>=1;--i){
int tmp=query1(root1,0,MAX,0,MAX-d[i].y);
minxy[flag+2][d[i].num]=min(MAX-d[i].x+MAX-d[i].y-tmp,INF);
change1(root1,0,MAX,MAX-d[i].y,MAX-d[i].x+MAX-d[i].y);
}
}
null2=seg2;
*null2={null2,null2,INF,INF,INF};
cnt2=0;
root2=newnode2();
for (int i=n,j=n;i>=1;--i){
ans2=min(ans2,query2(root2,0,MAX,d[i].y,MAX)-d[i].x-d[i].y);
if (d[i].y)
changey(root2,0,MAX,0,d[i].y-1,d[i].y);
changex(root2,0,MAX,d[i].y,d[i].x);
}
}
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
int xmin=MAX,xmax=-MAX,ymin=MAX,ymax=-MAX;
for (int i=1;i<=n;++i){
scanf("%d%d",&d[i].x,&d[i].y);
d[i].num=i;
xmin=min(xmin,d[i].x),xmax=max(xmax,d[i].x);
ymin=min(ymin,d[i].y),ymax=max(ymax,d[i].y);
}
for (int i=1;i<=n;++i)
ans1=max(ans1,max(d[i].x-xmin,xmax-d[i].x)+max(d[i].y-ymin,ymax-d[i].y));
work(0);
rever(-1,1),work(1);
rever(1,-1),work(2);
rever(-1,1),work(3);
for (int i=1;i<=n;++i)
ans2=min(ans2,min(minxy[0][i]+minxy[2][i],minxy[1][i]+minxy[3][i]));
printf("%d\n%d\n",ans1*2,ans2*2);
return 0;
}

好长啊……

实际上可以将第二种情况的线段树维护的最大值变成最小值,然后这两棵线段树就可以一起用了。

一起用的时候虽然会处理某些没有必要的东西,但是代码短啊……


总结

数据结构还是太菜了……

[JZOJ2866] 【集训队互测 2012】Bomb的更多相关文章

  1. 洛谷 P4463 - [集训队互测 2012] calc(多项式)

    题面传送门 & 加强版题面传送门 竟然能独立做出 jxd 互测的题(及其加强版),震撼震撼(((故写题解以祭之 首先由于 \(a_1,a_2,\cdots,a_n\) 互不相同,故可以考虑求出 ...

  2. [JZOJ2865]【集训队互测 2012】Attack

    题目 题目大意 平面上有一堆带权值的点.两种操作:交换两个点的权值,查找一个矩形的第\(k\)小 \(N<=60000\) \(M<=10000\) \(10000ms\) 思考历程&am ...

  3. P4463 [集训队互测2012] calc 拉格朗日插值 dp 多项式分析

    LINK:calc 容易得到一个nk的dp做法 同时发现走不通了 此时可以考虑暴力生成函数. 不过化简那套不太熟 且最后需要求多项式幂级数及多项式exp等难写的东西. 这里考虑观察优化dp的做法. 不 ...

  4. 【loj2461】【2018集训队互测Day 1】完美的队列

    #2461. 「2018 集训队互测 Day 1」完美的队列 传送门: https://loj.ac/problem/2461 题解: 直接做可能一次操作加入队列同时会弹出很多数字,无法维护:一个操作 ...

  5. 【2018集训队互测】【XSY3372】取石子

    题目来源:2018集训队互测 Round17 T2 题意: 题解: 显然我是不可能想出来的……但是觉得这题题解太神了就来搬(chao)一下……Orzpyz! 显然不会无解…… 为了方便计算石子个数,在 ...

  6. UOJ#191. 【集训队互测2016】Unknown 点分治 分治 整体二分 凸包 计算几何

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ191.html 题目传送门 - UOJ191 题意 自行移步集训队论文2016中罗哲正的论文. 题解 自行 ...

  7. LOJ3069. 「2019 集训队互测 Day 1」整点计数(min_25筛)

    题目链接 https://loj.ac/problem/3069 题解 复数真神奇. 一句话题意:令 \(f(x)\) 表示以原点 \((0, 0)\) 为圆心,半径为 \(x\) 的圆上的整点数量, ...

  8. UOJ#191. 【集训队互测2016】Unknown

    题意:维护一个数列,每个元素是个二维向量,每次可以在后面加一个元素或者删除一个元素.给定P(x,y),询问对于[l,r]区间内的元素$S_i$,$S_i \times P$的最大值是多少. 首先简单地 ...

  9. 【集训队互测2015】Robot

    题目描述 http://uoj.ac/problem/88 题解 维护两颗线段树,维护最大值和最小值,因为每次只有单点查询,所以可以直接在区间插入线段就可以了. 注意卡常,不要写STL,用链表把同类修 ...

随机推荐

  1. Avito Cool Challenge 2018 B - Farewell Party

    题目大意: 有n个人 接下来一行n个数a[i] 表示第i个人描述其他人有a[i]个的帽子跟他不一样 帽子编号为1~n 如果所有的描述都是正确的 输出possible 再输出一行b[i] 表示第i个人的 ...

  2. 让微信小程序页面之间的通信不在变得困难

    一个开始 小程序开发者总会碰到各种页面之间的通信问题,实现方式也五花八门,比如... 场景还原 首先这是一个电商小程序. 有这样一个需求: 首页某个地方要展示购物车商品数量. 当我在其他页面加购了商品 ...

  3. SingalR 构建 推送服务器初探

    项目需要用到推送,于是重新研究了下推送框架,最好能够独立成一个服务,与业务无关的服务,可以给所有的项目通用.找了好久最终决定用SinglR 框架. Signal 是微软支持的一个运行在 Dot NET ...

  4. K-mean matlab 实现代码

    一.K均值聚类算法 算法步骤如下: 1.初始化 已知数据集合X,及事先指定聚类的总类数N,在X中随机选取N个对象作为初始的聚类中心. 2.设定迭代终止条件 通常设置最大循环次数或者聚类中心的变化误差. ...

  5. 毫秒数转date

    毫秒数转date var date=new date(毫秒数);

  6. Vue登录登出以及JWT认证

    数据模型 主要用户名,密码,邮箱,头像,身份 const mongoose = require('mongoose') const schema = new mongoose.Schema({ use ...

  7. leetcood学习笔记-204-计算质数

    题目描述: 第一次提交;(超时): class Solution: def countPrimes(self, n: int) -> int: count = 0 for i in range( ...

  8. 扩展欧几里得原理的应用:POJ1061青蛙的约会

    /* POJ 1061: 青蛙的约会 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 123709 Accepted: 26395 ...

  9. ionic:创建 APP

    ylbtech-ionic:创建 APP 1.返回顶部 1. ionic 创建 APP 前面的章节中我们已经学会了 ionic 框架如何导入到项目中. 接下来我们将为大家介绍如何创建一个 ionic ...

  10. Python 数据结构_堆栈

    目录 目录 堆栈 堆栈 堆栈是一个后进先出(LIFO)的数据结构. 堆栈这个数据结构可以用于处理大部分具有后进先出的特性的程序流 . 在堆栈中, push 和 pop 是常用术语: push: 意思是 ...