bzoj2280[Poi2011]Plot 二分+倍增+二分+最小圆覆盖

Posted GXZlegend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj2280[Poi2011]Plot 二分+倍增+二分+最小圆覆盖相关的知识,希望对你有一定的参考价值。

题目描述

给出一系列点p_1, p_2, ... , p_n,将其分成不多余m个连续的段,第i段内求一个点q_i,使得q_i到这段内点的距离的最大值的最大值最小

输入

第一行,n m
下面n行,每行两个整数,表示p_i的x y坐标
1<=m<=n<=100000
坐标范围[-1000000,1000000] 0<p,q,r<=150,输入中至少包含一个’N’

输出

第一行,q_i到这段内点的距离的最大值的最大值的最小值
第二行,分成的段数k
下面k行,每行两个实数,表示q_k的x y坐标

All the real numbers should be printed with at most 15 digits after the decimal point. 

样例输入

7 2
2 0
0 4
4 4
4 2
8 2
11 3
14 2

样例输出

3.00000000
2
2.00000000 1.76393202
11.00000000 1.99998199


题解

二分+倍增+二分+最小圆覆盖

最大值最小,一眼二分答案。

考虑怎么判定:取前任意个点跑最小圆覆盖,直到半径大于mid为止,最后看分成段的数目是否超过m即可。

然而这样暴力跑是不行的,需要优化这个过程;同时直接再二分也是不可取的,因为二分的区间长度是满的,最坏情况下每段长度都为1,每段都需要$O(n\\log n)$的时间来找,GG。

所以需要让求最小圆覆盖的点数只与每段长有关。考虑倍增,处理出第一个$j$,满足$2^j$个不满足条件。然后在$2^{j-1}$到$2^j$之间二分处理即可。这样第一部分时间复杂度为$O(len)$,第二部分时间复杂度为$O(len\\log len)$,与n无关,所以可以满足时间要求。

因此总的时间复杂度为$O(n\\log^2n)$,然而常数巨大。。。所以才给300s。。。

最小圆覆盖的求法参见 http://www.cnblogs.com/GXZlegend/p/7467029.html 

CQzhangyu说卡精度,然而没看出来= = 1A了

#include <cstdio>
#include <algorithm>
#define N 100010
#define eps 1e-15
using namespace std;
int n , m , id[N] , tot;
double mid , x[N] , y[N] , ansx[N] , ansy[N];
inline double squ(double x)
{
	return x * x;
}
bool check(int lp , int rp , double &vx , double &vy)
{
	int i , j , k , len = rp - lp + 1;
	double px = 0 , py = 0 , r = 0 , x1 , x2 , x3 , y1 , y2 , y3;
	for(i = lp ; i <= rp ; i ++ ) id[i - lp + 1] = i;
	random_shuffle(id + 1 , id + len + 1);
	for(i = 1 ; i <= len ; i ++ )
	{
		if(squ(px - x[id[i]]) + squ(py - y[id[i]]) > r + eps)
		{
			px = x[id[i]] , py = y[id[i]] , r = 0;
			for(j = 1 ; j < i ; j ++ )
			{
				if(squ(px - x[id[j]]) + squ(py - y[id[j]]) > r + eps)
				{
					px = (x[id[i]] + x[id[j]]) / 2 , py = (y[id[i]] + y[id[j]]) / 2 , r = (squ(x[id[i]] - x[id[j]]) + squ(y[id[i]] - y[id[j]])) / 4;
					if(r > mid * mid + eps) return 0;
					for(k = 1 ; k < j ; k ++ )
					{
						if(squ(px - x[id[k]]) + squ(py - y[id[k]]) > r + eps)
						{
							x1 = x[id[i]] , x2 = x[id[j]] , x3 = x[id[k]];
							y1 = y[id[i]] , y2 = y[id[j]] , y3 = y[id[k]];
							px = (x1 * x1 * (y2 - y3) + x2 * x2 * (y3 - y1) + x3 * x3 * (y1 - y2) - (y1 - y2) * (y2 - y3) * (y3 - y1)) / (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2;
							py = ((x1 - x2) * (x2 - x3) * (x3 - x1) - y1 * y1 * (x2 - x3) - y2 * y2 * (x3 - x1) - y3 * y3 * (x1 - x2)) / (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2;
							r = squ(px - x1) + squ(py - y1);
							if(r > mid * mid + eps) return 0;
						}
					}
				}
			}
		}
	}
	vx = px , vy = py;
	return 1;
}
bool judge()
{
	int i , len , last , l , r , mid;
	bool flag;
	double tmpx , tmpy;
	tot = 0;
	for(i = 1 ; i <= n ; i = last + 1)
	{
		if(tot == m) return 0;
		flag = 0;
		for(len = 1 ; !flag ; len <<= 1)
		{
			if(!check(i , min(i + len - 1 , n) , tmpx , tmpy))
			{
				flag = 1;
				break;
			}
			else if(i + len - 1 >= n)
			{
				tot ++ , ansx[tot] = tmpx , ansy[tot] = tmpy;
				return 1;
			}
			l = i + len;
		}
		r = min(i + len - 1 , n) , last = l - 1;
		while(l <= r)
		{
			mid = (l + r) >> 1;
			if(check(i , mid , tmpx , tmpy)) last = mid , l = mid + 1;
			else r = mid - 1;
		}
		check(i , last , tmpx , tmpy) , tot ++ , ansx[tot] = tmpx , ansy[tot] = tmpy;
	}
	return 1;
}
int main()
{
	srand(20011011);
	int i , cnt = 50;
	double l = 0 , r = 2e6;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= n ; i ++ ) scanf("%lf%lf" , &x[i] , &y[i]);
	while(cnt -- )
	{
		mid = (l + r) / 2;
		if(judge()) r = mid;
		else l = mid;
	}
	printf("%.15lf\\n" , r);
	mid = r , judge();
	printf("%d\\n" , tot);
	for(i = 1 ; i <= tot ; i ++ ) printf("%.15lf %.15lf\\n" , ansx[i] , ansy[i]);
	return 0;
}

 

 

以上是关于bzoj2280[Poi2011]Plot 二分+倍增+二分+最小圆覆盖的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ2525[Poi2011]Dynamite 二分+树形DP

bzoj 2525: [Poi2011]Dynamite二分+树上贪心

做题POI2011R1 - Plot——最小圆覆盖&倍增

BZOJ2527: [Poi2011]Meteors

[POI2011]Plot

[bzoj2946][Poi2000]公共串_后缀数组_二分