BZOJ1137: [POI2009]Wsp 岛屿

111 篇文章 0 订阅
4 篇文章 0 订阅
题目大意:平面上给出N个岛屿形成一个凸多边形,两两岛屿之间有边相连,道路相交处视为分岔路口。现在有M条道路毁坏了,但是道路相交处仍可正常通行,求从1到N的最短距离

挺厉害的题...
首先由于是一个凸多边形,所以最优方案是沿着"完好无损的那些道路的半平面交"来走
(画个图感受一下还是挺明显的)
但是直接做是不行的,因为有O(N^2)个限制
所以我们考虑去掉一些无用的半平面...
还是由于这是一个凸多边形,所以对于一个点i,只有和他相连的标号最大的那个点与他形成的半平面是有用的,所以我们对于每个点只保留一个(i,last[i])的半平面就好了
这样半平面个数就降为了O(N)的

直接上半平面交就可以了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define N 100010
using namespace std;
vector<int>V[N];
int last[N];
struct node{double x,y;};
struct line{node a,b;double ang;};
node operator +(const node &x,const node &y){return (node){x.x+y.x,x.y+y.y};}
node operator -(const node &x,const node &y){return (node){x.x-y.x,x.y-y.y};}
node operator *(const node &x,const double &y){return (node){x.x*y,x.y*y};}
double xj(node x,node y){return x.x*y.y-x.y*y.x;}
bool cmp(line x,line y)
{
	if(x.ang!=y.ang) return x.ang<y.ang;
	return xj(y.b-x.a,y.a-x.a)>0;
}
double sqr(double x){return x*x;}
double dis(node x,node y){return sqrt(sqr(y.x-x.x)+sqr(y.y-x.y));}
bool left(node x,line y){return xj(y.a-x,y.b-x)>=0;}
void print(node x){cout<<x.x<<' '<<x.y<<' ';}
void print(line x){print(x.a);cout<<'*';print(x.b);cout<<endl;}
node jiao(line x,line y)
{
	double b=xj(x.b-x.a,y.a-x.a)/xj(y.b-y.a,x.b-x.a);
	return y.a+(y.b-y.a)*b;
}
node a[N];
line L[N];
line q[N];
int h,t;
node p[N];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	int i,j,x,y;
	for(i=1;i<=n;i++)
	scanf("%lf%lf",&a[i].x,&a[i].y);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		V[x].push_back(y);V[y].push_back(x);
	}
	for(i=1;i<=n;i++)
	{
		sort(V[i].begin(),V[i].end());
		int s=V[i].size();
		for(j=s-1;j>=0;j--)
		{
			if(V[i][j]!=n-s+j+1)
			{
				last[i]=n-s+j+1;
				break;
			}
		}
		if(j==-1) last[i]=n-s;
	}
	if(last[1]==n) {printf("%.9lf",dis(a[1],a[n]));return 0;}
	int cnt=0;
	for(i=1;i<=n;i++)
	if(last[i]>i)
	{
		cnt++;
		L[cnt].a=a[i];
		L[cnt].b=a[last[i]];
		L[cnt].ang=atan2(L[cnt].b.y-L[cnt].a.y,L[cnt].b.x-L[cnt].a.x);
	}
	cnt++;
	L[cnt].a=a[n];
	L[cnt].b=a[1];
	L[cnt].ang=atan2(L[cnt].b.y-L[cnt].a.y,L[cnt].b.x-L[cnt].a.x);
	sort(L+1,L+cnt+1,cmp);
	int tot=0;
	L[0].ang=-707;
	for(i=1;i<=cnt;i++)
	if(L[i].ang!=L[i-1].ang)
	tot++,L[tot]=L[i];
	cnt=tot;
	h=t=1;q[1]=L[1];
	for(i=2;i<=cnt;i++)
	{
		while(h<t&&left(p[t-1],L[i])) t--;
		while(h<t&&left(p[h],L[i])) h++;
		t++;q[t]=L[i];
		if(h<t) p[t-1]=jiao(q[t-1],q[t]);
	}
	while(h<t&&left(p[t-1],q[h])) t--;
	p[t]=jiao(q[t],q[h]);
	t++;p[t]=p[h];
	double ans=0;
	for(i=h;i<t;i++)
	ans+=dis(p[i],p[i+1]);
	printf("%.9lf",ans-dis(a[1],a[n]));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值