放牧代码和思想
专注自然语言处理、机器学习算法
    愛しさ 優しさ すべて投げ出してもいい

AOJ 2256 Divide the Cake 题解 《挑战程序设计竞赛》

目录

AOJ 2256 Divide the Cake

分蛋糕:长H宽W的长方形上有2n个草莓,求在两个边上任取两点构成直线恰好将草莓平分的概率?

3.6与平面和空间打交道的计算几何 

数值积分 

开学了,近乡情更怯,好难过,刷一题缓解一下。

在左边任取一点后,作出满足条件的直线,一定有两条直线是临界情况。让这一点在自己的的邻域上下移动,另一边的有效范围变化如图:

变化量为ε(W-x1)/x1 – ε(W-x2)/x2,对此积分即可。

然而并不是所有点的邻域移动都是上图的直线,对于某些点,临界情况是其他点,也就是x1,x2不同,积分表达式中的常量自然不同。

具体哪些点对应哪些临界情况,可以这么操作。任取两点构成一条直线,其在y轴上的截距相邻两两构成一个区间,该区间的点积分常量一定相同。

至于这个区间到底对应哪两个临界点,对所有直线排序后,取出极角在最中间的那两条即是。

#include <iostream>
#include <vector>
#include <complex>
#include <algorithm>
#include <cstdio>

using namespace std;

typedef complex<long double> P;

const long double EPS = 1e-10;

namespace std 
{
	bool operator <(const P &p1, const P &p2) 
	{
		if (p1.real() != p2.real()) return p1.real() < p2.real();
		return p1.imag() < p2.imag();
	}
};

// 按照y坐标排序
bool cmp(const P &p1, const P &p2) 
{
	return p1.imag() < p2.imag();
}

// 在pa和pb这两点决定的线上,横坐标为x的点的y坐标
long double calc_y_when_x_equals(const P &pa, const P &pb, long double x) 
{
	P d = pb - pa;
	if (abs(d.real()) < EPS) 
	{
		if (arg(d) < 0) return -EPS;
		else return EPS;
	}
	return pa.imag() + d.imag() * ((x - pa.real()) / d.real());
}

bool solve() 
{
	int W, H, N;
	cin >> W >> H >> N;
	if (!W && !H && !N) return false;
	N *= 2;

	vector<P> ps(N);
	for (int i = 0; i < N; ++i) 
	{
		int x, y;
		cin >> x >> y;
		ps[i] = P(x, y);
	}
	// 将四个顶点算在内的任意两点组成直线在y轴上的截距,它们相邻构成区间
	vector<P> events;
	for (int i = 0; i < N; ++i) 
	{
		for (int j = i + 1; j < N; ++j) 
		{
			events.push_back(P(0, calc_y_when_x_equals(ps[i], ps[j], 0)));
		}
		events.push_back(P(0, calc_y_when_x_equals(ps[i], P(W, 0), 0)));
		events.push_back(P(0, calc_y_when_x_equals(ps[i], P(W, H), 0)));
	}
	events.push_back(P(0, 0));
	events.push_back(P(0, H));
	sort(events.begin(), events.end(), cmp);
	// 区间收集完毕
	const int M = events.size();
	long double ans = 0;
	for (int i = 0; i < M - 1; ++i) 
	{
		const P &pa = events[i];
		const P &pb = events[i + 1];
		if (pa.imag() < 0) continue;
		if (pb.imag() > H) break;
		if (pb.imag() - pa.imag() < EPS) continue;
		const P m = (pa + pb) / (long double)2.0;
		long double upper = 0, lower = 0;
		vector<pair<long double, P>> v;
		for (const P &p : ps) 
		{
			const P d = p - m;
			v.push_back(make_pair(arg(d), p));	// 极角
		}
		sort(v.begin(), v.end());
		lower = min<long double>(H, max<long double>(0, calc_y_when_x_equals(m, v[N / 2 - 1].second, W)));	// 在右边的长度下界
		upper = min<long double>(H, max<long double>(0, calc_y_when_x_equals(m, v[N / 2].second, W)));		// 在右边的长度上界
		long double add = (pb.imag() - pa.imag()) * (upper - lower);	// 左边的长度乘以右边的长度,积分的思想
		ans += abs(add);
	}
	printf("%.10Lf\n", ans / H / H);
	return true;
}

///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
	cin.tie(0);
	ios::sync_with_stdio(0);

	while (solve());
	return 0;
}
///////////////////////////End Sub//////////////////////////////////

Reference

https://github.com/osak/Contest/blob/master/AOJ/2256.cc

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » AOJ 2256 Divide the Cake 题解 《挑战程序设计竞赛》

评论 欢迎留言

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

我的作品

HanLP自然语言处理包《自然语言处理入门》