【学习小记】半平面交——排序增量法

Preface

之前的半平面交的算法是基于分治和凸包合并的,分治两边,计算出半平面交,再合并凸包。
而这种排序增量法好写简洁常数小,适合在比赛中使用。

Text

为了避免半平面交区域无界的情况,我们在无穷远处四个方向加上四个半平面的限制。

可以看出,有限的半平面交是一个凸包

方便起见,我们用点+向量的形式来表示一个半平面,向量的左手向就是半平面的方向。
定义半平面的极角为向量的极角,我们将半平面按照极角排序,对于极角相同的,我们只保留最靠里,也就是限制最紧的那一条。

我们维护一个队列,像做凸壳一样的从队尾加入半平面,如果当前队尾半平面边界和队尾前一个的边界交点在新的半平面之外,说明队尾就没用了,将队尾弹掉。

但是半平面交既然是一个凸包,它会绕一圈回来,如果只在队尾加肯定是不对的。
弹完队尾之后,我们再判断队头的半平面是否也没用了,判定方法类似,如果能的话还要将队头的半平面给弹掉。

这样做完以后,我们发现最后队尾会剩下一些半平面,虽然这些半平面是凸的,但是它们有可能会被队头给弹掉,此时还要再判一下,将一些队尾弹掉。

分析复杂度,每一个半平面最多只会进队出队各一次,时间复杂度仅在于排序,为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

PS:尽量避免在排序重载compare函数的时候用atan2去比较,每次比较都要计算atan2是很慢的,我们可以提前计算好再比,或者用叉积+象限来比。

Code

丑到极致了…

namespace geometry
{
	const double eps=1e-10;
	const double pi=acos(-1);
	struct node
	{	
		double x,y;
		node(double _x=0,double _y=0){x=_x,y=_y;}
	};
	node operator +(node x,node y) {return node(x.x+y.x,x.y+y.y);}
	node operator -(node x,node y) {return node(x.x-y.x,x.y-y.y);}
	node operator *(double x,node y) {return node(x*y.x,x*y.y);}
	double crs(node x,node y) {return x.x*y.y-x.y*y.x;}
	double ang(node x) 
	{
		double v=atan2(x.y,x.x);
		if(v<0) v+=2*pi;
		return v;
	}

	struct line
	{
		node p,v;
		line(){};
		line(node _p,node _v) {p=_p,v=_v;}
	};
	node dot(line x,line y) {return y.p+(crs(x.v,x.p-y.p)/crs(x.v,y.v))*y.v;}
	bool inside(line x,node y)
	{
		return (crs(x.v,y-x.p)>eps);
	}
	bool in(line x,node y)
	{
		return abs(crs(x.v,y-x.p)<=eps);
	}
}
using namespace geometry;

int t,n,n1,n2;
node a[N],ab[N];
line d[4*N],db[4*N];
struct px
{
	double v;
	int p;
	friend bool operator <(px x,px y) {return x.v<y.v;}
}u2[N];


int pre(int x) {return (x==2)?n:x-1;}
int suf(int x) {return (x==n)?2:x+1;}

bool cmp2(px x,px y)
{
	return(x.v+eps<y.v||(abs(x.v-y.v)<=eps&&inside(d[y.p],d[x.p].p)));
}
double get()
{
	fo(i,1,n1) 
	{
		if(in(d[i],node(0,0))) return 0;
		u2[i]=(px){ang(d[i].v),i};
		db[i]=d[i];
	}
	static int dq[4*N],d2[4*N];
	n2=0;
	sort(u2+1,u2+n1+1,cmp2);

	fo(i,1,n1) d[i]=db[u2[i].p];
	
	fo(i,1,n1) if(i==1||fabs(ang(d[i].v)-ang(d[i-1].v))>eps) d2[++n2]=i;
	int l=1,r=2;dq[1]=d2[1],dq[2]=d2[2];
	fo(i1,3,n2)
	{
		int i=d2[i1];
		while(l<r&&!inside(d[i],dot(d[dq[r]],d[dq[r-1]]))) r--;
		while(l<r&&!inside(d[i],dot(d[dq[l]],d[dq[l+1]]))) l++;
		dq[++r]=i;
	}
	while(l+2<r&&!inside(d[dq[l]],dot(d[dq[r]],d[dq[r-1]]))) r--;
	double s=0;//计算面积
	fo(i,l,r-2) s+=crs(dot(d[dq[i]],d[dq[i+1]]),dot(d[dq[i+1]],d[dq[i+2]]));
	s+=crs(dot(d[dq[r-1]],d[dq[r]]),dot(d[dq[r]],d[dq[l]]));
	s+=crs(dot(d[dq[r]],d[dq[l]]),dot(d[dq[l]],d[dq[l+1]]));
	return s/2;
}

接入第三方登录是让用户方便快捷地使用已有账号登录你的网站或应用程序,提高用户体验的一种方式。本文将介绍如何使用 PHP 实现微信公众号第三方登录。 1. 获取微信授权 首先,需要获取微信用户的授权。具体步骤如下: 1)引导用户打开微信授权页面: ```php $appid = 'your_appid'; $redirect_uri = urlencode('http://yourdomain.com/callback.php'); $scope = 'snsapi_userinfo'; $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$appid&redirect_uri=$redirect_uri&response_type=code&scope=$scope&state=STATE#wechat_redirect"; header("Location: $url"); ``` 其中,`$appid` 是你的微信公众号的 AppID,`$redirect_uri` 是授权后回调的 URL,`$scope` 是授权作用域,可以是 `snsapi_base` 或 `snsapi_userinfo`,`$state` 是自定义参数,用于防止 CSRF 攻击。 2)获取授权码: 用户同意授权后,会重定向到 `$redirect_uri` 指定的 URL,带上授权码 `code` 和 `state` 参数。 ```php $code = $_GET['code']; $state = $_GET['state']; ``` 3)获取 access_token 和 openid: 使用授权码 `code` 获取 `access_token` 和 `openid`。 ```php $access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$secret&code=$code&grant_type=authorization_code"; $response = file_get_contents($access_token_url); $result = json_decode($response, true); $access_token = $result['access_token']; $openid = $result['openid']; ``` 其中,`$secret` 是你的微信公众号的 AppSecret。 2. 获取用户信息 获取到 `access_token` 和 `openid` 后,可以使用以下代码获取用户信息: ```php $userinfo_url = "https://api.weixin.qq.com/sns/userinfo?access_token=$access_token&openid=$openid&lang=zh_CN"; $response = file_get_contents($userinfo_url); $userinfo = json_decode($response, true); ``` 其中,`$userinfo` 包含用户的昵称、头像等信息。 3. 将用户信息保存到数据库 最后,将获取到的用户信息保存到数据库中,以便下次使用时快速登录。 ```php // 连接数据库 $con = mysqli_connect('localhost', 'username', 'password', 'database'); mysqli_set_charset($con, "utf8"); // 查询用户是否已存在 $sql = "SELECT * FROM users WHERE openid='$openid'"; $result = mysqli_query($con, $sql); if (mysqli_num_rows($result) == 0) { // 用户不存在,插入新用户信息 $nickname = mysqli_real_escape_string($con, $userinfo['nickname']); $headimgurl = mysqli_real_escape_string($con, $userinfo['headimgurl']); $sql = "INSERT INTO users (openid, nickname, headimgurl) VALUES ('$openid', '$nickname', '$headimgurl')"; mysqli_query($con, $sql); } // 保存用户登录状态 $_SESSION['openid'] = $openid; ``` 以上就是使用 PHP 实现微信公众号第三方登录的步骤。需要注意的是,为了确保安全性,应该对用户输入的数据进行过滤和验证,防止 SQL 注入和 XSS 攻击等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值