牛客练习赛48----D-小w的基站网络

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/923/D
来源:牛客网
涉及:单调栈


题目如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此题花了我两天时间,一直卡在case75%,我还以为啥没有考虑到,结果是神TM的排序comp函数写错了,哭了。


首先说明一下: a ⃗ × b ⃗ \vec a×\vec b a ×b 仍然是一个向量,这个向量的方向垂直 x O y xOy xOy平面.

a ⃗ = ( x 1 , y 1 ) , b ⃗ = ( x 2 , y 2 ) \vec a=(x_{1},y_{1}),\vec b=(x_{2},y_{2}) a =(x1,y1)b =(x2,y2),则 a ⃗ × b ⃗ \vec a×\vec b a ×b 向量模长为
∣ a ⃗ × b ⃗ ∣ = ∣ x 1 ∗ y 2 − x 2 ∗ y 1 ∣ |\vec a×\vec b|=|x_{1}*y_{2}-x_{2}*y_{1}| a ×b =x1y2x2y1
a ⃗ × b ⃗ \vec a×\vec b a ×b 向量方向判断:
 
 1.若 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) > 0 (x_{1}*y_{2}-x_{2}*y_{1})>0 (x1y2x2y1)>0则此向量方向朝上;
 
 2.若 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) &lt; 0 (x_{1}*y_{2}-x_{2}*y_{1})&lt;0 (x1y2x2y1)<0则此向量方向朝下;

 
 3.若 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) = 0 (x_{1}*y_{2}-x_{2}*y_{1})=0 (x1y2x2y1)=0则此向量为零向量;

按照题目意思,只有当 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) &gt; 0 (x_{1}*y_{2}-x_{2}*y_{1})&gt;0 (x1y2x2y1)>0才存在一条从 ( x 1 , y 1 ) (x_{1},y_{1}) (x1,y1) ( x 2 , y 2 ) (x_{2},y_{2}) (x2,y2)的边边权为 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) (x_{1}*y_{2}-x_{2}*y_{1}) (x1y2x2y1)

假设: a ⃗ \vec a a 与x轴正方向夹角为 θ 1 \theta_{1} θ1 b ⃗ \vec b b 与x轴正方向夹角为 θ 2 \theta_{2} θ2

那么 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) &gt; 0 等 价 于 θ 1 &lt; θ 2 (x_{1}*y_{2}-x_{2}*y_{1})&gt;0等价于\theta_{1}&lt;\theta_{2} (x1y2x2y1)>0θ1<θ2


按照上面红色字条件,可以一开始对所有向量按照与x正方向夹角进行递增的排序

inline bool comp(point a,point b){
	return 1.0*a.x/sqrt(a.x*a.x+a.y*a.y)>1.0*b.x/sqrt(b.x*b.x+b.y*b.y);//按照夹角余弦值从大到小排序
}

我这个排序是按照余弦从大到小排序的,由于y>0,则夹角的范围是(0,π),在(0,π),夹角越大,余弦值越小,所以把夹角余弦值按照由大到小排序。

存有向量(坐标点)的数组经过排序以后,那么数组中靠前的点都有通往数组中靠后的点的一条边。

注意:方向相同的两个向量所对应的两个点之间是没有边的!!

注意:排序前还要记录每个点的序号,可以弄一个如下的结构体,在输入数据的时候顺便记录序号

typedef struct op{
	ll x;//x值
	ll y;//y值
	int place;//序号
}point;
point po[1000006];
for(i=1;i<=n;i++){
	po[i].x=read();
	po[i].y=read();
	po[i].place=i;//记录序号
}

比如样例一中所有向量排序如下图所示:
在这里插入图片描述


排完序,可以继续探讨向量叉乘的集合意义:
 
 向量叉乘的的模长就等于以两个向量为边的三角形的面积的两倍

由于要求最短路径长,则让这个三角形的面积越小越好
 
 
举个例子,还是样例一,假如要求从起点(5号点)到2号点的最短路径,由图可知从5号点到2号线之间是有路径的
 
 1.如果直接从5号点到2号点,则如图所示,蓝色部分面积为6:
 在这里插入图片描述
 2.先经过4号点,再到2号点,如图所示,蓝色面积为3:

 在这里插入图片描述

综上所述:为了得到最小面积,必须让这个多边形形状越往下凹越好,换句话说,从起点开始所走的路径应该为一个向量递增的向量序列序列(并不是模长递增,起点向量不计入此序列),后面讨论什么叫向量递增。


要判断一个向量序列是否递增,此序列至少要有三个向量,如图所示:
在这里插入图片描述
由于向量3的终点,超过了向量4和5终点连线的边(红色线),那么说明这个向量序列中,从向量3到向量4不是递增的,也说明了从起点(向量5终点)到点4不需要经过向量3

又如图所示:
在这里插入图片描述
向量3还是超过了红色线,而向量4没有超过红色线,说明从点5(起点)到点2需要经过点4,不需要经过点3


不可能每次找最短路径都按照上面的方法来判断,那样绝对超时

为了找到这个多边形的凹壳,需要利用到单调栈,不知道单调栈的可以看看其他博客,单调栈就是栈内的元素保持单调增,或者单调减。

这个题我们需要维护单调增的单调栈,首先我们用一个数组dis来存起点到每个点的最短距离,用一个数now存目前单调栈从栈底元素(起点)栈顶元素最短路径

单调栈会有下列情况:
 
 1.开始栈是空的,遍历排序后的向量数组的向量,如果不是起点向量,则起点到此向量没有路径,赋值dis[]为-1
 
 2.遍历到了起点向量,赋值dis[]为0,并且把起点向量放到单调栈里
 
 3.继续遍历向量,如果此向量和起点向量同向,则起点到此点没有路径,赋值dis[]为-1
 
 4.遍历到第一个和起点向量不同向的向量,直接把此向量放到栈里面更新now值,并把now值赋给dis[]
 
 (下面这个是重点)
 5.后面继续遍历向量,此时栈里面有二个或者两个以上的向量,如果栈里除栈底向量(起点向量)以外的向量加上目前遍历的向量组成的向量序列递减的,就把栈顶向量移出栈并且更新now值然后继续判断直到栈内只剩起点向量或者栈内除栈底向量以外的向量加上目前遍历到的向量组成的向量序列递增的,然后就把目前遍历到的向量放入栈内更新now值,并把目前的now值赋给dis[]

代码和代码解释如下(注意起点向量一旦入栈,就永不出栈):

inline bool check(point a,point b,point c){//c是目前遍历到的向量,a是栈顶向量,b是栈内第二个向量
	if(getdis(a,c)<=getdis(a,b)+getdis(b,c))	return true;//如果发现a向量到c向量不是递增的,需要移出栈顶
	else	return false;//否则不移出
}
	memset(dis,-1,sizeof(dis));//首先把dis数组全部赋值为-1,后面再按照需求改
	int i=1;
	while(po[i].place!=k && i<=n)	i++;//这个是判断排序后向量数组中在起点向量之前的向量(都没有路径)
	dis[po[i].place]=0;sta.push(po[i]);//等遍历到了起点向量,就把dis[]赋值为0,并且把此向量放入栈中
	while(1.0*po[i].x/po[i].y==1.0*sta.top().x/sta.top().y && i<=n)	i++;//这个是判断和此向量同向的向量(也没有路径)
	for(;i<=n;i++){
		point p=po[i];//p是目前遍历到的向量
		point q1,q2;
		while(sta.size()>1){//如果栈内除了起点向量,还有其他向量,就满足循环条件
			q1=sta.top();//q1是栈顶向量
			sta.pop();
			q2=sta.top();//q2是栈中第二个向量
			sta.push(q1);	
			if(check(q2,q1,p)){//判断是否不满足单调增条件,注意是不满足
				now-=getdis(q2,q1);//不满足说明要移出栈顶,先更新now值
				sta.pop();//然后移出栈顶
			}	
			else	break;//满足条件,循环就结束了
		}
		now+=getdis(sta.top(),p);//先更新now值
		dis[p.place]=now;//把now值赋给dis[]
		sta.push(p); //把此向量移入栈
	}

下面讨论一些特殊情况

如果有两个同方向的向量,如下图所示
在这里插入图片描述
红色是目前遍历到的向量,蓝色是栈顶向量,为了获得一个凹壳,我们当然是要留红色的向量,为了方便,可以进行一下操作(重点)
 
 1.在排序comp函数中加入一条排序规则

if(1.0*a.x/a.y==1.0*b.x/b.y)	return a.x*a.x+a.y*a.y>b.x*b.x+b.y*b.y;
inline bool check(point a,point b,point c){
	if(1.0*c.x/c.y==1.0*b.x/b.y)	return true;// 不要问为啥把x放分母,这样做可以预防垂直的向量,反正y大于0
	if(getdis(a,c)<=getdis(a,b)+getdis(b,c))	return true;
	else	return false;
}

加了这条语句,就保证排序中,遇到两个同方向的向量,就把模长长的放前面,模长短的放后面
 
 2.在check函数中加入一条语句

if(1.0*c.x/c.y==1.0*b.x/b.y)	return true;
inline bool check(point a,point b,point c){
	if(1.0*c.x/c.y==1.0*b.x/b.y)	return true;
	if(getdis(a,c)<=getdis(a,b)+getdis(b,c))	return true;
	else	return false;
}

加了这条语句,就保证如果栈顶向量和遍历到的向量同向,就把栈顶向量移出栈

这两个操作配合起来,就保证几个同向的向量pk后,最后留下的肯定是最短的向量,同时还能更新这几个向量的最短路径长。


举个例子(还原过程,可无视)

向量有下列这些(已经排好序了)

3 1
3 3(这个是起点)
2 2
1 3
0 2
-2 4
-1 2
-1 1

如下图所示:
在这里插入图片描述

1.遍历第一个向量(3,1),不是起点向量,不用入栈,dis[1]=-1,now=0

----------------
i12345678
dis[i]-1
栈内(栈顶)

2.遍历第二个向量(3,3),是起点向量,入栈,dis[2]=0,now=0

----------------
i12345678
dis[i]-10
栈内(栈顶)(3,3)

3.遍历第三个向量(2,2),和起点向量同向,不用入栈,dis[3]=-1,now=0

----------------
i12345678
dis[i]-10-1
栈内(栈顶)(3,3)

4.遍历第四个向量(1,3),由于栈内只有一个元素,入栈,dis[4]=now=6

----------------
i12345678
dis[i]-10-16
栈内(栈顶)(1,3)(3,3)

5.遍历第五个向量(1,3),getdis(2,4)+get(4,5)>=getdis(2,5),栈顶4向量出栈,5向量入栈,dis[5]=now=6-6+6=6

----------------
i12345678
dis[i]-10-166
栈内(栈顶)(0,2)(3,3)

6.遍历第六个向量(-2,4),getdis(2,5)+get(5,6)<getdis(2,6),直接入栈,dis[6]=now=6+4=10

----------------
i12345678
dis[i]-10-16610
栈内(栈顶)(-2,4)(0,2)(3,3)

7.遍历第七个向量(-1,2),与栈顶6向量同向,栈顶6向量出栈,7向量入栈,dis[7]=now=10-4+2=8

----------------
i12345678
dis[i]-10-166108
栈内(栈顶)(-1,2)(0,2)(3,3)

8.遍历第八个向量(-1,1),getdis(5,7)+get(7,8)>=getdis(5,8),栈顶7向量出栈,now=8-2=6;
getdis(2,5)+get(5,8)>=getdis(2,8),栈顶5向量出栈,8向量入栈,now=6-6+6=6

----------------
i12345678
dis[i]-10-1661086
栈内(栈顶)(-1,1)(3,3)

代码如下:

#include <iostream>
#include <stack>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
typedef struct op{
	ll x;
	ll y;
	int place;
}point;
point po[1000006];
stack<point> sta;
ll dis[1000006];
ll now=0;
int n,k;
inline int read(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
inline bool comp(point a,point b){
	if(1.0*a.x/a.y==1.0*b.x/b.y)	return a.x*a.x+a.y*a.y>b.x*b.x+b.y*b.y;
	else return 1.0*a.x/sqrt(a.x*a.x+a.y*a.y)>1.0*b.x/sqrt(b.x*b.x+b.y*b.y);
}
inline ll getdis(point a,point b){return a.x*b.y-a.y*b.x;}
inline bool check(point a,point b,point c){
	if(1.0*c.x/c.y==1.0*b.x/b.y)	return true;
	if(getdis(a,c)<=getdis(a,b)+getdis(b,c))	return true;
	else	return false;
}
int main(){
	int i;
	memset(dis,-1,sizeof(dis));
	n=read();
	for(i=1;i<=n;i++){
		po[i].x=read();
		po[i].y=read();
		po[i].place=i;
	}
	k=read();point pk=po[k];
	sort(po+1,po+n+1,comp);
	i=1;
	while(po[i].place!=k && i<=n)	i++;
	dis[po[i].place]=0;sta.push(po[i]);
	while(1.0*po[i].x/po[i].y==1.0*sta.top().x/sta.top().y && i<=n)	i++;
	for(;i<=n;i++){
		point p=po[i];
		point q1,q2;
		while(sta.size()>1){
			q1=sta.top();
			sta.pop();
			q2=sta.top();
			sta.push(q1);	
			if(check(q2,q1,p)){
				now-=getdis(q2,q1);
				sta.pop();
			}	
			else	break;
		}
		now+=getdis(sta.top(),p);
		dis[p.place]=now;
		sta.push(p); 
	}
	for(i=1;i<=n;i++)	printf("%lld\n",dis[i]);
	return 0;
}

(加了一个快速写入,不然老是在超时的边缘徘徊,自己太菜 ),说实话没加快速读入是下面的样子
在这里插入图片描述
不知道为何发生这种玄学情况。。。。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值