我是靠谱客的博主 烂漫樱桃,最近开发中收集的这篇文章主要介绍2021-2022-1 ACM集训队每周程序设计竞赛(5) - 问题 F: 逃离 - 题解题目分析AC代码,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

传送门

    • 逃离
      • 题目描述
      • 输入描述
      • 输出描述
      • 样例一
        • 输入
        • 输出
      • 样例二
        • 输入
        • 输出
      • 提示
  • 题目分析
  • AC代码


逃离

  • CMP
  • 跳蛙
  • 剪切
  • 数学?
  • 数学!
  • 逃离

时间限制:20秒
空间限制:128M


题目描述

取经成功的斗战胜佛又犯了事,这次他召唤了很多分身来逃离如来佛主的五指山。

悟空召唤了 N − 1 N-1 N1个分身(加上真身,共有 N N N个孙猴子),每个猴子只能朝固定的一个方向移动,移动速度相同。

如来佛想要一掌压住所有的猴子,问你佛主的手掌的最小面积是多少。


为了简化问题,我们把天庭想象成一个二维坐标平面。

初始时,每个猴子都具有一个坐标和一个逃离方向:

  • U代表向上逃,即 y y y轴的正方向
  • D代表向下逃,即 y y y轴的负方向
  • L代表向左逃,即 x x x轴的负方向
  • R代表向右逃,即 x x x轴的正方向

如来佛主的手掌想象成一个矩形,要压住所有的猴子,即某时刻所有的猴子都在矩形之内(或边界上)


输入描述

第一行一个正整数 N N N,代表共有 N N N只猴子

接下来 N N N行,每行有空格隔开的两个正整数和一个字符,分别代表这只猴子初始位置的 x x x y y y坐标及其逃离方向。

输入格式如下:

N
x1 y1 d1
x2 y2 d2
...
xn yn dn

其中:

  • 1 ≤ N ≤ 1 0 5 1leq Nleq10^5 1N105
  • − 1 0 8 ≤ x i , y i ≤ 1 0 8 -10^8leq x_i, y_ileq 10^8 108xi,yi108
  • d i d_i di U U U, D D D, L L L R R R

输出描述

输出如来佛手掌的最小面积,答案保留6位小数。


样例一

输入
2
0 3 D
3 0 L
输出
0.000000

样例二

输入
5
-7 -10 U
7 -6 U
-8 7 D
-3 3 D
0 -6 R
输出
97.500000

提示

为了简化计算,如来佛的手掌只能是横着的矩形

题目即求整个过程中, ( x m a x − x m i n ) × ( y m a x − y m i n ) (x_{max}−x_{min})×(y_{max}−y_{min}) (xmaxxmin)×(ymaxymin)的最小值。

样例一中,
两只猴子会在 ( 0 , 0 ) (0,0) (0,0)处相遇,此时盖猴儿所需手掌的面积为0


题目分析

这道题主要有两种做法。一个是三分,一个是取特殊值。

取特殊值

只针对一个方向(比如水平方向),讨论最左边的猴子和最右边的猴子的距离(即为 x m a x − x m i n x_{max}−x_{min} xmaxxmin),记为 Δ x Delta x Δx

刚开始 Δ x Delta x Δx会减小一段时间(可能是0),之后 Δ x Delta x Δx会保持不变一段时间(可能是0),最后增大(可能是0)。

Δ x Delta x Δx的值变化趋势必是先减然后不变最后变大。

因此, Δ x Delta x Δx随着时间的变化而变化的函数如图所示:
在这里插入图片描述
比较重要的时刻也就4个端点。

同理,竖直方向的最大距离 Δ y Delta y Δy的变化趋势也如上图所示。

先讨论 Δ x Delta x Δx Δ y Delta y Δy同时减小的部分,此时 Δ x × Δ y Delta xtimesDelta y Δx×Δy的值一定是逐渐变小的。
同理 Δ x Delta x Δx Δ y Delta y Δy同时增加的部分,此时 Δ x × Δ y Delta xtimesDelta y Δx×Δy的值一定是逐渐增大的。
这两种情况下,矩形面积的最小值一定在直线端点处。

现在我们讨论 Δ x Delta x Δx Δ y Delta y Δy一个增加一个减少的情况。不失一般性,假设 Δ x Delta x Δx在减小而 Δ y Delta y Δy在增加。

如果 Δ x Delta x Δx减小的速率比 Δ y Delta y Δy增加的速率快,那么矩形的面积就不断减小。反之,若减小速率比增加速率慢,矩形面积就不断增加。

此时 Δ x × Δ y Delta xtimes Delta y Δx×Δy取最小值的时刻也是线段的断点处。

我们也可以写出 Δ x × Δ y Delta xtimes Delta y Δx×Δy的函数来得到 Δ x × Δ y Delta xtimes Delta y Δx×Δy的最小值。

总之,我们可以直接枚举所有 Δ x Delta x Δx的变化速率发生改变的时刻以及 Δ y Delta y Δy的变化速率发生改变的时刻,答案就在其中某个时刻。

Tips:
其实不管分身再多,很多猴子对答案都是没有影响的。我们只需要考虑几只特殊的猴子。

水平方向上,我们只需要考虑 4 4 4只猴子:

  • 最左边的往左逃的猴子
  • 最左边的往右逃的猴子
  • 最右边的往左逃的猴子
  • 最右边的往右逃的猴子

其他的猴子都始终在这4只猴子之内,就无需考虑了。
但此题时间限制较长,不优化也能在 10 10 10秒之内通过。

再加上竖直方向上的4只猴子,一共8只,能在几十毫秒内通过。

三分
如果不想像上述那样判断特殊时刻,我们也可以无脑三分。

简单讲述一下三分的思路:

对于一个 [ l , r ] [l,r] [l,r]上的凹函数,我们要求其最小值,可以每次在 [ l , r ] [l,r] [l,r]的两个三等分点处计算函数值,从而每次把函数的最小值逼近为原区间的 2 3 frac{2}{3} 32

在这里插入图片描述

如图所示,我们要求凹函数在 [ l , r ] [l,r] [l,r]上的最小值,每次只需要先求出点 l + r − l 3 l+frac{r-l}{3} l+3rl处的函数值和点 l + 2 × r − l 3 l+2timesfrac{r-l}{3} l+2×3rl的函数值。

如果点 l + r − l 3 l+frac{r-l}{3} l+3rl处的函数值比点 l + 2 × r − l 3 l+2timesfrac{r-l}{3} l+2×3rl的函数值更大,说明函数最小值在 l + r − l 3 l+frac{r-l}{3} l+3rl r r r之间。否则说明在 l l l l + 2 × r − l 3 l+2timesfrac{r-l}{3} l+2×3rl之间。
在这里插入图片描述

因此,我们每次更新 l l l或者 r r r的值,最小值所在区间就会变为原来的 2 3 frac{2}{3} 32

假如初始区间长度为 r − l r-l rl,那么经过 k k k次三分后,区间长度就会变为 ( r − l ) × ( 2 3 ) k (r-l)times(frac{2}{3})^k (rl)×(32)k

我们只需要进行一定次数的三分即可。比如进行 300 300 300次三分,最小值区间就会变为原来的 ( 2 3 ) 300 ≈ 1.488 × 1 0 − 53 (frac{2}{3})^{300}approx1.488times10^{-53} (32)3001.488×1053


AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n;
struct node {
    double x, y;
    char direction[3];
}a[100010];

double f(double t) { // 计算x秒时,最小的面积
    double mx = 1e10, Mx = -1e10, my = 1e10, My = -1e10; // 水平方向最小最大值的初始值、竖直方向最小最大值的初始值
    for (int i = 0; i < n ; i++) {
        double x = a[i].x, y = a[i].y;
        switch(a[i].direction[0]) {
            case 'U': y += t; break;
            case 'D': y -= t; break;
            case 'L': x -= t; break;
            case 'R': x += t; break;
        }
        mx = min(mx, x);
        Mx = max(Mx, x);
        my = min(my, y);
        My = max(My, y);
    }
    return (Mx - mx) * (My - my);
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        scanf("%lf%lf%s", &a[i].x, &a[i].y, a[i].direction);
    }
    double l = 0, r = 1e12, ans = 1e18; // 时间枚举的范围[0, 1e12],答案初始值1e18
    for (int i = 0; i < 300; i++) { // 三分300次
        double ml = l + (r - l) / 3, mr = l + 2 * (r - l) / 3;
        double ansl = f(ml), ansr = f(mr);
        if (ansl > ansr) l = ml;
        else r = mr;
        ans = min(ans, ansl), ans = min(ans, ansr);
    }
    printf("%.6lfn", ans);
    return 0;
}

用三分的方法同样可以优化为只考虑8只猴子,东子哥优化后只用了几十毫秒。

大神代码↓

#include<bits/stdc++.h>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<map>
#include<set>
#include<cstdlib>
#include<bitset>
#include<math.h>
#define mall (node*)malloc(sizeof(node))
using namespace std;
typedef long long ll;
ll n;
struct node{
    long double x1=1e18,x2=-1e18,y1=1e18,y2=-1e18;
    bool use=0;
}a[4];
long double d[4][2]={0,1,0,-1,-1,0,1,0};
long double f1(long double t){
    long double x1=1e18,x2=-1e18,y1=1e18,y2=-1e18;
    for(ll i=0;i<4;i++){
        if(a[i].use==0) continue;
        node n2;
        n2.x1=a[i].x1+d[i][0]*t;
        n2.x2=a[i].x2+d[i][0]*t;
        n2.y1=a[i].y1+d[i][1]*t;
        n2.y2=a[i].y2+d[i][1]*t;
        x1=min(x1,n2.x1);
        x2=max(x2,n2.x2);
        y1=min(y1,n2.y1);
        y2=max(y2,n2.y2);
    }
//    cout<<x1<<" "<<x2<<" "<<y1<<" "<<y2<<endl;
    return (x2-x1)*(y2-y1);
}
long double f(long double l,long double r){
    long double ml=l+(r-l)/3.0;
    long double mr=r-(r-l)/3.0;
    // printf("1 - %.8f %.8f %.8f %.8fn",l,ml,mr,r);
    // printf("2 - %f %fn",f1(ml),f1(mr));
    if(l+1e-11>=r) return (l+r)/2.0;
    if(f1(ml)>f1(mr)) return f(ml,r);
    else return f(l,mr);
}
int main(void)
{
    scanf("%lld",&n);
    for(ll i=0;i<n;i++){
        double x,y;char ch;
        scanf("%lf%lf %c",&x,&y,&ch);
        ll cc=0;
        long double x1=x,y1=y;
        if(ch=='U') cc=0;
        else if(ch=='D') cc=1;
        else if(ch=='L') cc=2;
        else cc=3;
        a[cc].use=1;
        a[cc].x1=min(a[cc].x1,x1);
        a[cc].x2=max(a[cc].x2,x1);
        a[cc].y1=min(a[cc].y1,y1);
        a[cc].y2=max(a[cc].y2,y1);
    }
    // for(ll i=0;i<4;i++){
    //     printf("%f %f %f %fn",a[i].x1,a[i].x2,a[i].y1,a[i].y2);
    // }
    double ans=f1(f(0,1e10));
    printf("%.6f",ans);
}

注意如果不适用次数而使用区间范围进行递归的话,可能需要使用long double才能消除足够的精度误差。

原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/120898079

最后

以上就是烂漫樱桃为你收集整理的2021-2022-1 ACM集训队每周程序设计竞赛(5) - 问题 F: 逃离 - 题解题目分析AC代码的全部内容,希望文章能够帮你解决2021-2022-1 ACM集训队每周程序设计竞赛(5) - 问题 F: 逃离 - 题解题目分析AC代码所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(75)

评论列表共有 0 条评论

立即
投稿
返回
顶部