BZOJ4691 Let There Be Light

我怎么感觉此题难点在于判断线段是否与圆的边有交……让我想起了冷冻波那题……

我们考虑状压DP,f[i][j]表示前i个气球,保证至少j集合内的光源能照到固定点,最少要拿掉多少个气球,然后每次进来一个气球,我们算他会挡住哪些光源的光,这样有了一个他会挡住的集合d,然后枚举状态j,如果j&d!=0,f[i][j]=f[i-1][j]+1,否则f[i][j]=f[i-1][j]

至于如何判线段与圆的边是否有交

首先要满足条件线段所在直线与圆心的距离小于等于圆的半径,这个我觉得用余弦定理算比较简单

其次还要满足两个端点不能都在园内,这个可以直接判

还要满足如果两个点都在圆外的话,那么过圆心做线段所在直线的垂线,垂线必须与线段有交

都在园外先判出来,然后我是连接两个端点和圆心构成一个三角形,如果两个端点所在的角有一个是钝角,那么就不能满足这个条件。判钝角可以用余弦定理然后判cos是否小于0

综上所述,余弦定理真是好东西-_-

最后把所有f值满足要求的状态算一下取个max即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<map>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
using namespace std;
#define MAXN 2010
#define MAXM 20
#define MAXD 35010
#define INF 1000000000
#define MOD 1000000007
#define eps 1e-8
#define ll long long
struct pt{
	double x;
	double y;
	double z;
};
double sq(double x){
	return x*x;
}
double dis(pt x,pt y){
	return sqrt(sq(x.x-y.x)+sq(x.y-y.y)+sq(x.z-y.z));
}
double cal(pt A,pt B,pt O){
	double a=dis(A,O);
	double b=dis(A,B);
	double c=dis(B,O);
	double t=(sq(a)+sq(b)-sq(c))/(2*b);
	return sqrt(sq(a)-sq(t));
}
bool jud(pt A,pt B,pt O){
	double a=dis(A,B);
	double b=dis(A,O);
	double c=dis(B,O);
	return (sq(a)+sq(b)-sq(c))/(2*a*b)<0;
}
int n,m,lim;
pt l[MAXM],p[MAXN];
double v[MAXM],r[MAXN];
int f[MAXD];
pt ljss;
int main(){
	int i,j;
	while(scanf("%d%d%d",&n,&m,&lim)){
		if(!n&&!m&&!lim){
			break;
		}
		memset(f,0,sizeof(f));
		for(i=1;i<=n;i++){
			scanf("%lf%lf%lf%lf",&p[i].x,&p[i].y,&p[i].z,&r[i]);
		}
		for(i=1;i<=m;i++){
			scanf("%lf%lf%lf%lf",&l[i].x,&l[i].y,&l[i].z,&v[i]);
		}
		scanf("%lf%lf%lf",&ljss.x,&ljss.y,&ljss.z);
		int N=1<<m;
		for(i=1;i<=n;i++){
			int d=0;
			for(j=1;j<=m;j++){
				if(cal(l[j],ljss,p[i])<=r[i]&&
				   !(dis(ljss,p[i])<=r[i]&&dis(l[j],p[i])<=r[i])&&
				   !((jud(ljss,l[j],p[i])||jud(l[j],ljss,p[i]))&&dis(ljss,p[i])>=r[i]&&dis(l[j],p[i])>=r[i])){
					d|=(1<<(j-1));
				}
			}
			for(j=0;j<N;j++){
				if(j&d){
					f[j]++;
				}
			}
		}
		double ans=0;
		for(i=0;i<N;i++){
			if(f[i]<=lim){
				double t=0;
				for(j=1;j<=m;j++){
					if(i&(1<<(j-1))){
						t+=v[j]/sq(dis(l[j],ljss));
					}
				}
				ans=max(ans,t);
			}
		}
		printf("%.6lf\n",ans);
	}
	return 0;
}

/*
12 5 4
0 10 0 1
1 5 0 2
1 4 0 2
0 0 0 2
10 0 0 1
3 -1 0 2
5 -1 0 2
10 10 0 15
0 -10 0 1
10 -10 0 1
-10 -10 0 1
10 10 0 1
0 10 0 240
10 0 0 200
10 -2 0 52
-10 0 0 100
1 1 0 2
0 0 0
12 5 4
0 10 0 1
1 5 0 2
1 4 0 2
0 0 0 2
10 0 0 1
3 -1 0 2
5 -1 0 2
10 10 0 15
0 -10 0 1
10 -10 0 1
-10 -10 0 1
10 10 0 1
0 10 0 260
10 0 0 200
10 -2 0 52
-10 0 0 100
1 1 0 2
0 0 0
5 1 3
1 2 0 2
-1 8 -1 8
-2 -3 5 6
-2 1 3 3
-4 2 3 5
1 1 2 7
0 0 0
5 1 2
1 2 0 2
-1 8 -1 8
-2 -3 5 6
-2 1 3 3
-4 2 3 5
1 1 2 7
0 0 0
0 0 0 
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值