BUPT OJ102 最远距离

44 篇文章 3 订阅

题目描述

正义的伙伴褋祈和葬仪社的机器人Fuyuneru正在被邪恶的GHQ部队追杀。眼看着快要逃不掉了,祈就把重要的东西塞到了机器人体内,让它先跑,自己吸引火力。

假设Fuyuneru带上东西开始逃跑时所处的点为原点,朝向为正北。操纵FuyuNeru的指令有如下四种:

right X: X是1-359之间的整数,Fuyuneru的前进方向顺时针转X度。

left X: X是1-359之间的整数,Fuyuneru的前进方向逆时针转X度。

forward X: X是整数(0<=X<=1000),Fuyuneru向当前朝向前进X米。

backward X: X是整数(0<=X<=1000),Fuyuneru向当前朝向后退X米。

现在祈向Fuyuneru体内输入了N(1<=N<=50)个这样的指令。可是由于此前Fuyuneru被GHQ部队击中,它出了一点小问题:这N个指令执行的顺序是不确定的。

问:Fuyuneru最远可能逃出多远?

即,Fuyuneru在执行完N条指令之后,距离原点最远的可能距离是多少?

输入格式

第一行是一个整数T,代表测试数据有T组。

每组测试数据中,第一行是一个整数N,代表指令有N条;

随后紧跟N行,每一行代表一个指令(格式保证是上述四种中的一种,数据保证合法)

输出格式

对于每组数据,输出一行:最远的可能逃亡距离,精确到小数点后3位。

输入样例

3
3
forward 100
backward 100
left 90
4
left 45
forward 100
right 45
forward 100
6
left 10
forward 40
right 30
left 10
backward 4
forward 4

输出样例

141.421
200.000
40.585

这题居然卡了我一个晚上...气死我了, 还是各种不熟练orz

题目意思是怎样使葬仪社的机器人End Rave, 啊不对, 是Fuyuneru逃得越远越好

所以一眼看到这题, 我认为这是一道几何题, 以下是当时随手注释的思路:

/*
基本思路:
在题目中共有四种操作50条指令, 可简化为仅四条顺序指令:forward X1, left X2, backward X3, left X3
证明方法为余弦定理. 所以可以合并所有的forward与backward
转向操作分为有意义转向与无意义转向, 易知forward操作与backward操作之间为有意义转向
有意义转向满足的要求为尽可能接近180+360*N, 这一点通过取模运算实现
无意义转向无特别要求
现在所使用的方法是通过搜索角度数组ang[]的全排列, 得到模360后最接近180度的转角和, 然后通过余弦定理得到最远距离
*/
思路没错, 证明部分也不提, 可以说是很快的想到了解决方案. 

但是最后算法的实现出了一个问题,  而且我也一直没看出来, 导致最后五次WA还是没有发现问题核心

当时我实现全排列时代码是这样子的:

        For(i,0,k){
            forang+=ang[i]; //forang存储前i+1个转角和
            curang=forang%360;
            minang=MIN(minang,abs(180-curang));
            For(m,0,i+1){
                curang=(360+curang-ang[m])%360;
                For(j,i+1,k){
                    curang+=ang[j]; //将m+1位替换为j+1位
                    minang=MIN(minang,abs(180-curang%360)); //全排列求最接近180的角度解
                    curang=(360+curang-ang[j])%360; //返回对curang的修改
                }
                curang+=ang[m]; //返回对curang的修改
            }
        }

乍一看没什么错误, 把浮点误差和负数整除也完全考虑到了, 自己所编译测试的所有数据点也过了, 那么问题在哪里呢?

问题就是事实上我没有写出正确的全排列, 替换所能完成的序列集合必须包括前i+1个ang[]中的i个. 所以实现的全排列本身是错误的

事实上, 我们知道 50C25 约等于 1e14, 所以全排列不仅不能很好的实现, 而且即使实现也会TLE 

所以, 算法的选择上完全失误了. 

之后我换了另一种方法来解决问题. 注意到所有角度都是整数, 所以可以列出通过当前转向角度x后能达到的所有x的集合dp, 在这里引入vis数组避免重复记录和运算.

全新的代码如下, 删去了无用的ang[]数组并简化了代码后运行时间缩短至5ms, 还是很满意的.

刚刚想到解法然后敲出代码怎么可能睡得着呢...我还是先休息一会吧


/*
USER_ID: test#birdstorm
PROBLEM: 102
SUBMISSION_TIME: 2014-03-05 14:55:17
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define For(i,m,n) for(i=m;i<n;i++)
#define MIN(x,y) (x)<(y)?(x):(y)
#define PI acos(-1.0)
 
int vis[360], dp[360];
 
main()
{
    int j, n, a, tmp, t;
    char temp[10];
    double x, forward, backward, minang;
    scanf("%d",&t);
    while(t--){
        minang=180; forward=backward=0;
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        scanf("%d",&n);
        while(n--){
            scanf("%s%d",temp,&a);
            if(!strcmp(temp,"forward")) forward+=a;
            if(!strcmp(temp,"backward")) backward+=a;
            if(!strcmp(temp,"right")||!strcmp(temp,"left"))
            {
                memset(vis,0,sizeof(vis));
                a=!strcmp(temp,"right")?a:360-a;
                For(j,0,360){
                    tmp=(j+a)%360;
                    if(!vis[j]&&dp[j]&&!dp[tmp]){
                        dp[tmp]++; vis[tmp]=1;
                        minang=MIN(minang,abs(180-tmp));
                    }
                }
            }
        }
        x=forward*forward+backward*backward-2*forward*backward*cos((180.0-minang)/180.0*PI);
        printf("%.3lf\n",sqrt(x));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值