题目描述
正义的伙伴褋祈和葬仪社的机器人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;
}