POJ - 1328 还是区间贪心+数学思维

题意大致是,x-y坐标系中,x轴上方有n个点,

那么此时让你在x轴上选若干点,以半径为d作圆,使得上方的点都坐落在圆内,并且作的圆数量最少.

一般这种在若干东西里选取若干,使得选取数量最少,一般是贪心.

这题看着不好求解,那么数学上进行转化.

对于x轴上方的点p,显然如果纵坐标y大于d时,无法被圆心在x轴上的圆圈住,此时输出-1

那么对于点p(x,y),数学角度上,建立三角形,delta=d*d-y*y,以区间[x-delta,x+delta]上的点作为圆心的圆肯定是可以包围p的.

这样就转化成区间贪心,若干区间,选取/标记最少的点,让所有区间都能被标记.

然后发现对一系列区间,如果一个区间和其他很多不同区间有重复部分,只要任选一个有交集的区间,

重复上一个操作,然后直到取不到为止

最后发现,无论是如何取有交集的区间,最后ans的值是一样的.

感觉可以用动态规划的思想证明这个贪心策略

然后直接暴力,1e6不会超时

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn=100010;
const int inf=0x3f3f3f3f; 
struct node {
	double	l,r;
}a[maxn];
bool cmp(const node &a,const node &b){
	return a.l<b.l||(a.l==b.l&&a.r>b.r);
}
int n,cnt;
double x,y,d;
bool vis[maxn];
queue <int> que;

int main()
{
	while(cin>>n>>d&&(n||d)){
		int ans=0;
		memset(vis,0,sizeof(vis));
		bool flag=0;
		for(int i=0;i<n;i++){
			cin>>x>>y;
			if(fabs(y)>d) flag=1;
			double de=sqrt(d*d-y*y);
			a[i].l=x-de,a[i].r=x+de;
		}
		if(flag){
			printf("Case %d: -1\n",++cnt);
			continue;
		}
		sort(a,a+n,cmp);
		for(int i=0;i<n;i++){
			if(vis[i]) continue;
			while(que.size()) que.pop();
			que.push(i);
			ans++;
			double l=a[i].l,r=a[i].r;
			for(int j=0;j<n;j++){
				if(vis[j]) continue;
				double  ll=a[j].l,rr=a[j].r;
				if(ll<=r){
					l=min(l,ll),r=min(r,rr);
					que.push(j);
				}
			}
			while(que.size()){
				int t=que.front();que.pop();
				vis[t]=1;
			} 
		}
		printf("Case %d: %d\n",++cnt,ans);
	}
	return 0;	
} 

这题一开始wa,原因竟然是我在ans=-1情况下直接输出-1...

emmm

看了题解才发现有o(n)的做法.....

这种做法基本就是poj3069的变形了

大致思路一样,就是不从区间入手,从选取的点入手,因为一段区间必然要选取某个点,那么我们每次都选择最优的.

显然这种区间问题,要先进行排序,然后选取区间的端点值,这样在有序的情况下,端点值一定是局部最优,因此推出整体最优

先对l进行降序排序,然后选取第一个点的l作为此时该区间的最优点,(显然,此时对于后面的区间,最左端覆盖得的区域最大,因此贪心策略是正确的)

然后找到无法被囊括的区间,此时再去更换temp

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn=100010;
const int inf=0x3f3f3f3f; 
struct node {
	double	l,r;
}a[maxn];
bool cmp(const node &a,const node &b){
	return a.l>b.l;
}
int n,cnt;
double x,y,d;

int main()
{
	while(cin>>n>>d&&(n||d)){
		int ans=1;
		bool flag=0;
		for(int i=0;i<n;i++){
			cin>>x>>y;
			if(fabs(y)>d) flag=1;
			double de=sqrt(d*d-y*y);
			a[i].l=x-de,a[i].r=x+de;
		}
		if(flag){
			printf("Case %d: -1\n",++cnt);
			continue;
		}
		sort(a,a+n,cmp);
		double t=a[0].l;
		for(int i=1;i<n;i++){
			if(a[i].r<t){
				ans++;
				t=a[i].l;
			}
		}
		printf("Case %d: %d\n",++cnt,ans);
	}
	return 0;	
} 

无论是时间复杂度还是代码可读性都有大大的提升.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值