zz: http://www.strongczq.com/2012/03/srm538-div1-2-turtlespy.html
题目原文:http://community.topcoder.com/stat?c=problem_statement&pm=11704&rd=14729
题目原文:http://community.topcoder.com/stat?c=problem_statement&pm=11704&rd=14729
题目大意:
机器人从起始点开始,可以有个四种动作:
- 前进X距离
- 后退X距离
- 向左旋转X度(1到359之间)
- 向右旋转X度(1到359之间)
现在给出一组动作的集合,动作的顺序未知,求该组动作按照一定的次序排列后得到的最大移动距离。
数据规模:X为[1,1000],动作集合大小为[1,50]
思路:
求解这道题的关键在于要观察到如下特点:
- 旋转的动作是可选的,因为可以把这些旋转动作放在最后,那么对最终位置就不会有任何影响。
- 最优移动策略中,前进的动作应该是连续的,后退的动作也应该是连续的。也就是说最优路径应该如下图所示,其中蓝色是前进的路线,红色是后退的路线。其中theta夹角表示两条路线之间旋转过的角度。
第二个特点,看起来比较直观。凭直觉的话,不同的前进(或者后退)动作之间如果加入了旋转动作,那么走出来的路线是曲折的路线,距离必然会小于走直线的路线,所以最优路线应该是先往一个方向前进再往一个方向后退。虽然该命题是正确的(因为通过system test了),但是这种分析并不严谨,需要更加严格的证明,目前本人没想出证明方法。
建立在这两个特点之上,我们所要解决的问题其实就是找到一个合适的theta使得最终距离最大,根据余弦定理:
c=sqrt(a^2+b^2-2*a*b*cost(theta))
theta越接近180度,c的值越大。所以我们需要从所有旋转角度的动作中找到一个组合使得theta的值最接近180度。
由于角度的取值范围只能是[0,359]中的整数,所以可以用dp算法获得所有可能的角度值,然后选取其中最接近180度的。dp过程与背包问题类似,每考虑一个旋转动作记录一次已有的角度值即可。算法复杂度O(360*n),其中n为旋转动作数。
Java代码:
public class TurtleSpy {
public double maxDistance(String[] commands) {
long f = 0, b = 0;
ArrayList<Integer> angle = new ArrayList<Integer>();
for (int i = 0; i < commands.length; ++i) {
String[] ss = commands[i].split(" ");
int x = Integer.parseInt(ss[1]);
if (ss[0].equals("forward")) {
f += x;
} else if (ss[0].equals("backward")) {
b += x;
} else if (ss[0].equals("left")) {
angle.add(-x);
} else {
angle.add(x);
}
}
boolean[] dp = new boolean[360];
dp[0] = true;
for (int i = 0; i < angle.size(); ++i) {
boolean[] next = new boolean[360];
for (int a = 0; a < 360; ++a){
if (dp[a]) {
next[(a + 360 + angle.get(i)) % 360] |= dp[a];
next[a] |= dp[a];
}
}
dp = next;
}
int theta = 0;
for (; theta < 180; ++theta) {
if (dp[180 - theta] || dp[180 + theta]) {
break;
}
}
return Math.sqrt(Math.abs(f * f + b * b + 2 * f * b * Math.cos(Math.PI * theta / 180)));
}
}