洛谷 P4529 [SCOI2003]切割多边形

时间:2020-03-26
本文章向大家介绍洛谷 P4529 [SCOI2003]切割多边形,主要包括洛谷 P4529 [SCOI2003]切割多边形使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

首先此题有一个显而易见的策略:每次切割都沿多边形的边缘。
那么就可以状压 dp 求解最小切割线,具体怎么转移也不必啰嗦了。
唯一的难点在于求解单次切割的长度。
不难发现,切 \(FG\) 的切割线正好是 \(FG\) 所在直线与已有的切割线集合 \(\{KJ, IL, AB, BC, CD, AD\}\) 的两个交点 \(M, N\) 之间的线段 \(MN\)。可以枚举所有已有切割线,求出 \(M, N\)

为了表达没有斜率的直线,我使用一般式方程。解方程的时候一定要特判除 \(0\)、平行等毒瘤情况。

#include <cstdio>
#include <cmath>
#include <algorithm>

inline double min(const double& a, const double& b){
	return a < b ? a : b;
}

const int MAXQ = 8 + 1;

double dp[1 << 8];

int n, m, q;

struct Point{
	double x, y;
	Point(){}
	Point(double a, double b){
		x = a, y = b;
	}
	bool operator<(const Point& b)const{
		return std::fabs(x - b.x) < 1e-12 ? y < b.y : x < b.x;
	}
	bool operator>(const Point& b)const{
		return std::fabs(x - b.x) < 1e-12 ? y > b.y : x > b.x;
	}
}p[MAXQ];

struct Line{
	double a, b, c;//一般式
	Line(){}
	Line(double __a, double __b, double __c){//参数构造直线
		a = __a, b = __b, c = __c;
	}
	Line(Point __a, Point __b){//两点构造直线
		if(std::fabs(__a.x - __b.x) < 1e-12)
			a = 1, b = 0, c = -__a.x;
		else
			a = -__a.y + __b.y, b = __a.x - __b.x, c = -a * __a.x - b * __a.y;
	}
	Point operator^(const Line& __b)const{//重载 ^ 运算符求解两直线交点
		if(std::fabs(b) < 1e-12 || std::fabs(__b.b) < 1e-12){
			if(std::fabs(b) < 1e-12 && std::fabs(__b.b) < 1e-12)
				return Point(1e18, 1e18);
			else if(std::fabs(b) < 1e-12){
				double x = -c / a, y = -(__b.a * x + __b.c) / __b.b;
				return Point(x, y);
			}
			else{
				double x = -__b.c / __b.a, y = -(a * x + c) / b;
				return Point(x, y);
			}
		}
		else if(std::fabs(a / b - __b.a / __b.b) < 1e-12)
			return Point(1e18, 1e18);
		else{
			double x = -(c * __b.b - __b.c * b) / (a * __b.b - __b.a * b),
				y = -(a * x + c) / b;
			return Point(x, y);
		}
	}
}line[MAXQ];

inline double dist(Point a, Point b){
	double x = a.x - b.x, y = a.y - b.y;
	x *= x, y *= y;
	return std::sqrt(x + y);
}

double value(int status, int node){
	Point tmp, l = p[node], r = p[node % q + 1], re_l, re_r,
		mid = Point((l.x + r.x) / 2, (l.y + r.y) / 2);
//re_l, re_r 是切割线与已有切痕的两个交点
	if(std::fabs(line[node].a) < 1e-12)
		re_l = line[node] ^ Line(1, 0, 0),
		re_r = line[node] ^ Line(1, 0, -n);
	else if(std::fabs(line[node].b) < 1e-12)
		re_l = line[node] ^ Line(0, 1, 0),
		re_r = line[node] ^ Line(0, 1, -m);
	else if(line[node].a / line[node].b > 0)
		re_l = std::max(line[node] ^ Line(0, 1, -m), line[node] ^ Line(1, 0, 0)),
		re_r = std::min(line[node] ^ Line(0, 1, 0), line[node] ^ Line(1, 0, -n));
	else if(line[node].a / line[node].b < 0)
		re_l = std::max(line[node] ^ Line(0, 1, 0), line[node] ^ Line(1, 0, 0)),
		re_r = std::min(line[node] ^ Line(0, 1, -m), line[node] ^ Line(1, 0, -n));
	for(int i = 1; i <= q; ++i)
		if(status & (1 << i - 1)){
			tmp = line[node] ^ line[i];
			if(tmp < mid)
				re_l = std::max(re_l, tmp);
			else if(tmp > mid)
				re_r = std::min(re_r, tmp);
		}
	return dist(re_l, re_r);
}

int main(){
	std::scanf("%d%d%d", &n, &m, &q);
	for(int i = 1; i <= q; ++i)
		std::scanf("%lf%lf", &p[i].x, &p[i].y);
	for(int i = 1; i <= q; ++i)
		line[i] = Line(p[i], p[i % q + 1]);
	for(int i = 1; i < (1 << q); ++i){
		dp[i] = 1e18;
		for(int j = 1; j <= q; ++j)
			if(i & (1 << j - 1))
				dp[i] = min(dp[i], dp[i ^ (1 << j - 1)] + value(i ^ (1 << j - 1), j));//朴素状压
	}
	std::printf("%lf\n", dp[(1 << q) - 1]);
	return 0;
}

时间复杂度大概是 \(O(2 ^ n)\)

原文地址:https://www.cnblogs.com/natsuka/p/12574930.html