BZOJ1038 [ZJOI2008]瞭望塔

Description

  致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔,以此加强村中的治安。我们
将H村抽象为一维的轮廓。如下图所示 我们可以用一条山的上方轮廓折线(x1, y1), (x2, y2), …. (xn, yn)来描
述H村的形状,这里x1 < x2 < …< xn。瞭望塔可以建造在[x1, xn]间的任意位置, 但必须满足从瞭望塔的顶端可
以看到H村的任意位置。可见在不同的位置建造瞭望塔,所需要建造的高度是不同的。为了节省开支,dadzhi村长
希望建造的塔高度尽可能小。请你写一个程序,帮助dadzhi村长计算塔的最小高度。

Input

  第一行包含一个整数n,表示轮廓折线的节点数目。接下来第一行n个整数, 为x1 ~ xn. 第三行n个整数,为y1
 ~ yn。

Output

  仅包含一个实数,为塔的最小高度,精确到小数点后三位。

Sample Input

【输入样例一】
6
1 2 4 5 6 7
1 2 2 4 2 1
【输入样例二】
4
10 20 49 59
0 10 10 0

Sample Output

【输出样例一】
1.000
【输出样例二】
14.500

HINT

 N ≤ 300,输入坐标绝对值不超过106,注意考虑实数误差带来的问题。

题解

可以发现,答案必定在任意两点连线的上方(不然会挡住视线)。

再进一步,可以发现答案只要满足在任意相邻两点上方,就一定满足上述条件。

因此只需要求出$n-1$个半平面的交,再枚举在已知点或半平面交点处建筑瞭望塔即可。

附代码:

#include <algorithm>
#include <cstdio>
using std::min;
using std::max;
const double eps = 1e-6;
int cmp(double l) {
  if (l > eps) return 1;
  if (l < eps) return -1;
  return 0;
}
struct Point{
  double x, y;
};
struct Line{
  double k, b;
  Line() : k(.0), b(.0) {}
  Line(const Point &a, const Point &b) {
    k = (a.y - b.y) / (a.x - b.x);
    this->b = a.y - a.x * k;
  }
  friend inline bool operator<(const Line &a, const Line &b) {
    if (cmp(a.k - b.k)) return a.k < b.k;
    return a.b > b.b;
  }
  double operator()(double x) const {
    return k * x + b;
  }
};
double crossing(const Line &a, const Line &b) {
  return (a.b - b.b) / (b. k - a.k);
}
const int N = 305;
Point p[N];
Line l[N], s[N];
int main() {
  int n;
  scanf("%d", &n);
  for (int i = 0; i < n; ++i) scanf("%lf", &p[i].x);
  for (int i = 0; i < n; ++i) scanf("%lf", &p[i].y);
  for (int i = 1; i < n; ++i) l[i - 1] = Line(p[i - 1], p[i]);
  std::sort(l, l + n - 1);
  int m = 0;
  for (int i = 0; i < n - 1; ++i) {
    while (m > 1 && crossing(s[m - 1], l[i]) <= crossing(s[m - 1], s[m - 2])) --m;
    s[m++] = l[i];
  }
  double ans = 1e10;
  int j = 0;
  for (int i = 0; i < n; ++i) {
    while (j < m - 1 && p[i].x > crossing(s[j], s[j + 1])) ++j;
    ans = min(ans, s[j](p[i].x) - p[i].y);
  }
  j = 1;
  for (int i = 0; i < m - 1; ++i) {
    double x = crossing(s[i], s[i + 1]);
    if (x > p[n - 1].x) break;
    while (p[j].x < x) ++j;
    ans = min(ans, s[i](x) - Line(p[j - 1], p[j])(x));
  }
  printf("%.3lf\n", ans);
  return 0;
}

(写了一遍一直调了一个小时没有A。。。重写一遍A。。。)

转载于:https://www.cnblogs.com/y-clever/p/6991259.html

  • 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、付费专栏及课程。

余额充值