照亮体育馆 Barisal Stadium

本文详细解析了UVA10641题目的算法解决方案,通过定义坐标、费用、照射范围等关键概念,利用叉乘判断光照角度,实现体育馆照明最优配置。介绍了IsShineOnCur、RangeCur、dp等变量的作用,以及初始化、转移方程的计算过程。
摘要由CSDN通过智能技术生成

在这里插入图片描述
UVA10641
题目为逆时针顺序编号,这里直接将数组开两倍来处理环。(然而不知为啥开到1000也能过)
定义
C o r n e r s [ i ] Corners[i] Corners[i]为体育馆点的坐标。
L i g h t s [ i ] Lights[i] Lights[i]为灯的坐标及费用。
I s S h i n e O n C u r [ i ] IsShineOn_{Cur}[i] IsShineOnCur[i]表示第 C u r Cur Cur盏灯是否能照到 i i i,用于计算范围。
R a n g e C u r Range_{Cur} RangeCur为第 C u r Cur Cur盏灯的照射范围,从 L e f t Left Left R i g h t Right Right,用于初始化 d p dp dp
d p [ L e f t ] [ R i g h t ] dp[Left][Right] dp[Left][Right]为从第 L e f t Left Left盏灯照到第 R i g h t Right Right盏灯的最少花费。

初始化 I s S h i n e O n C u r [ i ] \pmb{IsShineOn_{Cur}[i]} IsShineOnCur[i]IsShineOnCur[i]IsShineOnCur[i]
A 、 B A、B AB两点为体育馆的两点, C C C为体育馆的一盏灯,那么 C C C要想照亮边 A B AB AB,那么必须有 0 ∘ < ∠ B A C < 18 0 ∘ 0^{\circ}< ∠BAC< 180^{\circ} 0<BAC<180(手动转一转就知道了)
在这里插入图片描述
因为有:
s i n ( ∠ B A C ) = A B ⃗ × A C ⃗ sin(∠BAC)=\vec{AB}×\vec{AC} sin(BAC)=AB ×AC 并且当 s i n θ > 0 sin\theta>0 sinθ>0时, θ ∈ ( 0 ∘ , 18 0 ∘ ) \theta\in(0^{\circ},180^{\circ}) θ(0,180)
因此只要叉乘大于0即可。注意是逆时针的顺序。

//Cur为灯编号
void InitIsShineOn(const int& Cur) {
	memset(IsShineOn, false, sizeof(IsShineOn));
	for (int i = 1; i <= N; ++i) {
		//i以及i的下一个点位体育馆的点
		int&& Position = i % N + 1;
		//如果叉乘大于0
		IsShineOn[i] =
			(Lights[Cur].x - Corners[i].x) * (Corners[Position].y - Corners[i].y)
			>
			(Corners[Position].x - Corners[i].x) * (Lights[Cur].y - Corners[i].y);
	}
	return;
}

初始化 R a n g e C u r \pmb{Range_{Cur}} RangeCurRangeCurRangeCur
扫一遍即可

void InitRange(const int& Cur) {
	if (IsShineOn[1] && IsShineOn[N]) {
		Range.Left = N;
		Range.Right = 1;
		while (IsShineOn[Range.Left]) {
			--Range.Left;
		}
		while (IsShineOn[Range.Right]) {
			++Range.Right;
		}
		++Range.Left;
		--Range.Right;
		Range.Right += N;
	}
	else {
		Range.Left = 1;
		Range.Right = N;
		while (!IsShineOn[Range.Left]) {
			++Range.Left;
		}
		while (!IsShineOn[Range.Right]) {
			--Range.Right;
		}
	}
	return;
}

dp初始化
d p = i n f i f ( 第 C u r 盏 灯 能 照 亮 L e f t 到 R i g h t ) d p [ L e f t ] [ R i g h t ] = m i n ( d p [ L e f t ] [ R i g h t ] , L i g h t s [ i ] . C o s t ] ) dp=inf\\if(第Cur盏灯能照亮Left到Right)\\dp[Left][Right]=min(dp[Left][Right],Lights[i].Cost]) dp=infif(CurLeftRight)dp[Left][Right]=min(dp[Left][Right],Lights[i].Cost])

//判断第Cur盏灯能否照亮(Left,Right)的范围
bool Judgt(const int& Left, const int& Right) {
	return 		
		Range.Left - N <= Left && Range.Right - N >= Right
		|| 
		Range.Left <= Left && Range.Right >= Right
		|| 
		Range.Left + N <= Left && Range.Right + N >= Right;
}
void InitDP(const int& Cur) {
	for (int Length = 0; Length < N; ++Length) {
		for (int Left = 1; Left <= 2 * N - 1; ++Left) {
			int&& Right = Left + Length;
			if (Right > 2 * N - 1) {
				break;
			}
			if (Judgt(Left, Right)) {
				dp[Left][Right] = min(dp[Left][Right], Lights[Cur].Cost);
			}
		}
	}
	return;
}

转移方程
d p [ L e f t ] [ R i g h t ] = m i n ( d p [ L e f t ] [ k ] + d p [ k + 1 ] [ R i g h t ] ) L e f t ≤ k < R i g h t dp[Left][Right]=min(dp[Left][k]+dp[k+1][Right])\\Left\leq k<Right dp[Left][Right]=min(dp[Left][k]+dp[k+1][Right])Leftk<Right经典的区间 d p dp dp
最后答案为 m i n ( d p [ i ] [ i + N − 1 ] ) , ( 1 ≤ 1 ≤ N ) min(dp[i][i+N-1]),(1\leq 1\leq N) min(dp[i][i+N1]),(11N)(双倍数组处理环的答案)
AC代码

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
constexpr static long long inf = 0x3f3f3f3f3f3f3f3f;
struct Corner {
	double x, y;
}Corners[31];
struct Light {
	double x, y;
	long long Cost;
}Lights[1001];
struct {
	int Left, Right;
}Range;
int N, M;
bool IsShineOn[1001]{ false };
long long dp[1001][1001];
bool Input() {
	cin >> N;
	if (!N) {
		return false;
	}
	for (int i = 1; i <= N; ++i) {
		cin >> Corners[i].x >> Corners[i].y;
	}
	cin >> M;
	for (int i = 1; i <= M; ++i) {
		cin >> Lights[i].x >> Lights[i].y >> Lights[i].Cost;
	}
	return true;
}
void InitIsShineOn(const int& Cur) {
	memset(IsShineOn, false, sizeof(IsShineOn));
	for (int i = 1; i <= N; ++i) {
		int&& Position = i % N + 1;
		IsShineOn[i] =
			(Lights[Cur].x - Corners[i].x) * (Corners[Position].y - Corners[i].y)
			>
			(Corners[Position].x - Corners[i].x) * (Lights[Cur].y - Corners[i].y);
	}
	return;
}
void InitRange(const int& Cur) {
	if (IsShineOn[1] && IsShineOn[N]) {
		Range.Left = N;
		Range.Right = 1;
		while (IsShineOn[Range.Left]) {
			--Range.Left;
		}
		while (IsShineOn[Range.Right]) {
			++Range.Right;
		}
		++Range.Left;
		--Range.Right;
		Range.Right += N;
	}
	else {
		Range.Left = 1;
		Range.Right = N;
		while (!IsShineOn[Range.Left]) {
			++Range.Left;
		}
		while (!IsShineOn[Range.Right]) {
			--Range.Right;
		}
	}
	return;
}
bool Judgt(const int& Left, const int& Right) {
	return 		
		Range.Left - N <= Left && Range.Right - N >= Right
		|| 
		Range.Left <= Left && Range.Right >= Right
		|| 
		Range.Left + N <= Left && Range.Right + N >= Right;
}
void InitDP(const int& Cur) {
	for (int Length = 0; Length < N; ++Length) {
		for (int Left = 1; Left <= 2 * N - 1; ++Left) {
			int&& Right = Left + Length;
			if (Right > 2 * N - 1) {
				break;
			}
			if (Judgt(Left, Right)) {
				dp[Left][Right] = min(dp[Left][Right], Lights[Cur].Cost);
			}
		}
	}
	return;
}
void Compute() {
	for (int Length = 0; Length < N; ++Length) {
		for (int Left = 1; Left <= 2 * N - 1; ++Left) {
			int&& Right = Left + Length;
			if (Right > 2 * N - 1) {
				break;
			}
			for (int k = Left; k < Right; ++k) {
				if (dp[Left][k] == inf && dp[k + 1][Right] == inf) {
					continue;
				}
				dp[Left][Right] = min(dp[Left][Right], dp[Left][k] + dp[k + 1][Right]);
			}
		}
	}
}
long long DP() {
	memset(dp, 0x3f, sizeof(dp));
	for (int i = 1; i <= M; ++i) {
		InitIsShineOn(i);
		InitRange(i);
		InitDP(i);
	}		
	Compute();
	long long Ans = inf;
	for (int i = 1; i <= N; ++i) {
		Ans = min(Ans, dp[i][i + N - 1]);
	}
	return Ans;
}
int main() {
	while (Input()) {
		long long&& Ans = DP();
		if (Ans == inf) {
			puts("Impossible.");
		}
		else {
			cout << Ans << endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值