几个礼拜前参加了阿里巴巴的实习生校园招聘,不过在面试关跪了,一起去的同学过的也不多,看来阿里巴巴的要求还是有点高的。之前的笔试难度并不是特别大,主要内容是数据结构、算法等,其中有道题目挺有趣的,想和大家分享一下。题目是这样的:
有4个人在晚上想过一座桥,不过只有一个手电筒,一次只能过2个人,4个人单独过的话分别用时1、2、5、10分钟,2人一起过按慢的时间算,问最短要用多少时间?
不妨分别给人编号为A、B、C、D,看到题目的最直观想法就是让手电筒传递的时间尽量少,所以一种方案为:AB过,A回,AC过,A回,AD过。总耗时为2+1+5+1+10=19分钟。乍一看,答案就应该是这样吧,不过这里还有一种跟好的解法:AB过,A回,CD过,B回,AB过。总耗时为2+1+10+2+2=17分钟,比我们之前一个方案减少了2分钟。到这里,已经无法再举出耗时更少的例子了,看来17分钟时最短的时间。
---------------------------------------------------------------------------------------------------------------------------------
现在人数只有4个,我们可以采用枚举的办法,但是当人数变成40个,400个呢?显然枚举是难以完成的,必须使用一定的方法才能解决这一问题,。POJ1700,就是这么一道题目,人数<1000,单独用时<100,我们现在以过这道题为目标。
仔细想想,想得到最短时间,无非是两种方法(还用刚刚的例子,假设AB已先过桥,T()代表总用时,t()代表单独用时):
1:A回,AC过,T(ABC)=T(AB)+t(A)+t(C)
2:A回,CD过,B回,AB过,T(ABCD)=T(AB)+t(A)+t(D)+t(B)+t(B)
看到这里,隐约感觉到动态规划的气息。假设我们有N个人,把所有人的耗时存入一个有序数组time[ ],而dp[ i]代表过前i个人所耗的最小时间,则有递推公式:
dp[i] = min(dp[i-2]+time[0]+time[i]+time[1]*2 , dp[i-1]+time[0]+time[i])
好了,看来现在我们已经可以着手编程了,以下是用C++实现的代码:
#include<iostream>
#include<algorithm>
#define min(a,b) (a)<(b)?(a):(b)
using namespace std;
int main()
{
int T,n,i,time[1002],dp[1002];
cin >>T;
while(T--)
{
cin >> n;
for(i=0;i<n;i++)
cin >>time[i];
sort(time,time + n);
dp[0]=time[0] , dp[1]=time[1];
for(i=2;i<n;i++)
dp[i] = min(dp[i-2]+time[0]+time[i]+time[1]*2 , dp[i-1]+time[0]+time[i]);
cout <<dp[n-1] << endl;
}
return 0;
}
代码可以Ac,而且非常短小易懂,可以说我们已经解决了这个问题。但是仔细推敲以下,可以发现有个细节可以优化以下,那就是dp数组,我们其实只需要记录三个值就够了,那就是dp[N], dp[N+1], dp[N+2],所以就有了以下优化后的代码:
<pre code_snippet_id="315856" snippet_file_name="blog_20140427_2_8245384" name="code" class="cpp">#include<iostream>
#include<algorithm>
#define min(a,b) (a)<(b)?(a):(b)
using namespace std;
int main()
{
int T,n,i,time[1001],dp[3];
cin >> T;
while(T--)
{
cin >> n;
for(i=0;i<n;i++)
cin >>time[i];
sort(time,time + n);
dp[0]=time[0],dp[1]=time[1];
for(i=2;i<n;i++)
dp[i%3] = min(dp[(i-2)%3]+time[0]+time[i]+time[1]*2 , dp[(i-1)%3]+time[0]+time[i]); //这是一个滚动数组
cout <<dp[(n-1)%3] << endl;
}
return 0;
}
其实dp的方法不止这一种,读者可以开动大脑,应该可以想到其他解法:)。