BZOJ2280: [Poi2011]Plot

111 篇文章 0 订阅
14 篇文章 0 订阅

题目大意:给定n个点和m,要求把n个点分成m段,使得每段的最小覆盖圆的半径的最大值最小


肯定能想到要二分答案,然后看看怎么判定

对于每个点来说,需要找到序列中最远能到达的从他开始符合条件的点

但是因为最小圆覆盖需要随机打乱点才能保证时间复杂度,所以我们不能依次推出最小覆盖圆,那么能想到二分

但是每次检验是O(长度)的,而我们需要二分m次,直接二分可能会使时间复杂度变成O(logINF*NMlogN)

所以我们需要用到一些小技巧,就是先检验长度为1的方案,然后检验长度为2的方案,然后4,8.....直到找到一个不合法的k

然后我们紧接着在[k/2,k]这个区间里二分就可以了

因为这样这次搜索的二分上限为O(KlogN),而对答案的贡献至少为K,所以当对答案的贡献为N(即扫到最后)时,时间复杂度上限为O(NlogN)

所以总时间复杂度变成了O(NlogNlogINF)


PS:这题在POI上单点220s!!!!!!!BZOJ总时限300s差评!!害的我卡了好几发OJ,最后调了调eps,300.198s卡时过了....


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100010
#define eps 2e-8
using namespace std;
struct node{long double x,y;};
long double sqr(long double x){return x*x;}
long double dis(node x,node y)
{
	return sqrt(sqr(x.x-y.x)+sqr(x.y-y.y));
}
node a[N],b[N];
int n,m;
node O;
long double R;
node jfc(long double a,long double b,long double c,long double d,long double e,long double f)
{
	long double y=(a*f-c*d)/(b*d-a*e);
	long double x=(b*f-c*e)/(a*e-b*d);
	return (node){x,y};
}
bool incircle(node x)
{
	if(dis(x,O)<=R+eps) return true;
	return false;
}
void solve(int l,int r)
{
	int i,j,k;
	for(i=l;i<=r;i++)
	b[i-l+1]=a[i];
	int nn=r-l+1;
	random_shuffle(b+1,b+nn+1);
	R=0;
	for(i=1;i<=nn;i++)
	if(!incircle(b[i]))
	{
		O=b[i];R=0;
		for(j=1;j<i;j++)
		if(!incircle(b[j]))
		{
			O.x=(b[i].x+b[j].x)/2;
			O.y=(b[i].y+b[j].y)/2;
			R=dis(O,b[i]);
			for(k=1;k<j;k++)
			if(!incircle(b[k]))
			{
				O=jfc(
				b[i].x-b[j].x,b[i].y-b[j].y,(sqr(b[j].x)+sqr(b[j].y)-sqr(b[i].x)-sqr(b[i].y))/2,
				b[i].x-b[k].x,b[i].y-b[k].y,(sqr(b[k].x)+sqr(b[k].y)-sqr(b[i].x)-sqr(b[i].y))/2
				);
				R=dis(O,b[i]);
			}
		}
	}
}
int far(int st,long double limit)
{
	int i,j;
	for(i=1;;i++)
	{
		solve(st,min(st+(1<<i)-1,n));
		if(R>limit+eps) break;
		if(st+(1<<i)-1>=n) return n;
	}
	int l=st+(1<<(i-1)),r=min(st+(1<<i)-1,n),mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		solve(st,mid);
		if(R>limit+eps) r=mid;
		else l=mid+1;
	}
	return l-1;
}
bool judge(long double limit)
{
	int i,j,tot=0;
	for(i=1;i<=n;i=j+1)
	{
		if(tot==m) return false;
		j=far(i,limit);
		tot++;
	}
	return true;
}
int main()
{
	/*freopen("wyk.in","r",stdin);
	freopen("wyk.out","w",stdout);*/
	scanf("%d%d",&n,&m);
	int i,j;
	double tmp1,tmp2;
	for(i=1;i<=n;i++)
	{
		scanf("%lf%lf",&tmp1,&tmp2);
		a[i].x=tmp1;a[i].y=tmp2;
	}
	solve(1,n);
	long double l=0,r=R,mid;
	while(r-l>eps)
	{
		mid=(l+r)/2;
		if(judge(mid)) r=mid;
		else l=mid;
	}
	printf("%.15lf\n%d\n",(double)l,m);
	j=1;
	for(i=1;i<=m;i++)
	{
		if(j>n){puts("0 0");continue;}
		int tmp=far(j,l+eps);
		solve(j,tmp);
		printf("%.15lf %.15lf\n",(double)O.x,(double)O.y);
		j=tmp+1;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值