BZOJ 1043 HAOI 2008 下落的圆盘 计算几何

题目大意:给出一些圆盘,他们按照时间顺序相互覆盖,问最后的到的图形的可见圆周的周长是多少。


前言:円盘反对!让我们一起团结起来!赶走円盘!

思路:对于每一个圆盘,只要扫描在它后面出现的圆与它交的部分的并,总周长-相交的并就是剩下能看见的圆周的长度,然后累加到答案中。

对于两个圆的交,我们可以用一个有序数对(x,y)以弧度为单位来表示,这样所有的xy都在0~2π区间之内。求角度就利用余弦定理,见下图:


 ∠EAC就是我们要求的角。由于我们知道|AE|和|EC|分别是两个圆的半径,|AC|是圆心的距离,边都知道了就可以用余弦定理来求解任意一个角了。知道了这个角的大小,用向量(A->C)的极角加上∠EAC就是E点所代表的位置,减去∠EAC就是F点所代表的位置。这样就可以表示出两圆相交的区间了。
但是我们要保证所有的端点都在[0,2π]的区间之内,所以如果有加爆了或者减爆了的区间,要把他们分成两个区间。最后一步就是排序,然后求区间的并,计算可见的圆周长。

CODE:


#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <algorithm>
#define PI acos(-1.0)
#define MAX 1010
using namespace std;

struct Point{
	double x,y;

	Point(double _ = .0,double __ = .0):x(_),y(__) {}
	Point operator -(const Point &a)const {
		return Point(x - a.x,y - a.y);
	}
	void Read() {
		scanf("%lf%lf",&x,&y);
	}
};
struct Circle{
	Point o;
	double r;

	void Read() {
		scanf("%lf",&r);
		o.Read();
	}
}circle[MAX];

struct Interval{
	double st,ed;

	Interval(double _ = .0,double __ = .0):st(_),ed(__) {}
	bool operator <(const Interval &a)const {
		if(st == a.st)	return ed < a.ed;
		return st < a.st;
	}
}interval[MAX];

int circles,cnt;
double ans;

inline double Calc(const Point &p1,const Point &p2)
{
	return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

inline void InsertInterval(const Circle &a,const Circle &b)
{
	double dis = Calc(a.o,b.o);
	if(dis > a.r + b.r)	return ;
	if(dis < a.r - b.r)	return ;
	if(dis < b.r - a.r) {
		interval[++cnt] = Interval(.0,2.0 * PI);
		return ;
	}
	Point u = b.o - a.o;
	double alpha = atan2(u.y,u.x) + PI;
	double detla = acos((a.r * a.r + dis * dis - b.r * b.r) / (2.0 * a.r * dis));
	if(alpha - detla < 0) {
		interval[++cnt] = Interval(alpha - detla + 2.0 * PI,2.0 * PI);
		interval[++cnt] = Interval(0,alpha + detla);
	}
	else if(alpha + detla > 2.0 * PI) {
		interval[++cnt] = Interval(alpha - detla,2.0 * PI);
		interval[++cnt] = Interval(0,alpha + detla - 2.0 * PI);
	}
	else
		interval[++cnt] = Interval(alpha - detla,alpha + detla);
}

int main()
{
	cin >> circles;
	for(int i = 1; i <= circles; ++i)
		circle[i].Read();
	for(int i = circles; i; --i) {
		cnt = 0;
		for(int j = i + 1; j <= circles; ++j)
			InsertInterval(circle[i],circle[j]);
		sort(interval + 1,interval + cnt + 1);
		double start = .0,end = .0;
		double length = .0;
		for(int j = 1; j <= cnt; ++j)
			if(interval[j].st > end)
				length += end - start,start = interval[j].st,end = interval[j].ed;
			else
				end = max(end,interval[j].ed);
		length += end - start;
		length = PI * 2.0 - length;
		ans += length * circle[i].r;
	}
	cout << fixed << setprecision(3) << ans << endl;
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值